/*!
* Simditor v2.3.6
* http://simditor.tower.im/
* 2015-12-21
*/
(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module unless amdModuleId is set
    define('simditor', ['jquery', 'simple-module', 'simple-hotkeys', 'simple-uploader'], function ($, SimpleModule, simpleHotkeys, simpleUploader) {
      return (root['Simditor'] = factory($, SimpleModule, simpleHotkeys, simpleUploader))
    })
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like environments that support module.exports,
    // like Node.
    module.exports = factory(require('jquery'), require('simple-module'), require('simple-hotkeys'), require('simple-uploader'))
  } else {
    root['Simditor'] = factory(jQuery, SimpleModule, simple.hotkeys, simple.uploader)
  }
}(this, function ($, SimpleModule, simpleHotkeys, simpleUploader) {
  var AlignmentButton, BlockquoteButton, BoldButton, Button, Clipboard, CodeButton, CodePopover, ColorButton, FontScaleButton, Formatter, HrButton, ImageButton, ImagePopover, IndentButton, Indentation, InputManager, ItalicButton, Keystroke, LinkButton, LinkPopover, ListButton, OrderListButton, OutdentButton, Popover, Selection, Simditor, StrikethroughButton, TableButton, TitleButton, Toolbar, UnderlineButton, UndoManager, UnorderListButton, Util,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key] } function ctor() { this.constructor = child } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child },
    hasProp = {}.hasOwnProperty,
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i } return -1 },
    slice = [].slice

  Selection = (function(superClass) {
    extend(Selection, superClass)

    function Selection() {
      return Selection.__super__.constructor.apply(this, arguments)
    }

    Selection.pluginName = 'Selection'

    Selection.prototype._range = null

    Selection.prototype._startNodes = null

    Selection.prototype._endNodes = null

    Selection.prototype._containerNode = null

    Selection.prototype._nodes = null

    Selection.prototype._blockNodes = null

    Selection.prototype._rootNodes = null

    Selection.prototype._init = function() {
      this.editor = this._module
      this._selection = document.getSelection()
      this.editor.on('selectionchanged', (function(_this) {
        return function(e) {
          _this.reset()
          return _this._range = _this._selection.getRangeAt(0)
        }
      })(this))
      return this.editor.on('blur', (function(_this) {
        return function(e) {
          return _this.reset()
        }
      })(this))
    }

    Selection.prototype.reset = function() {
      this._range = null
      this._startNodes = null
      this._endNodes = null
      this._containerNode = null
      this._nodes = null
      this._blockNodes = null
      return this._rootNodes = null
    }

    Selection.prototype.clear = function() {
      var e
      try {
        this._selection.removeAllRanges()
      } catch (_error) {
        e = _error
      }
      return this.reset()
    }

    Selection.prototype.range = function(range) {
      var ffOrIE
      if (range) {
        this.clear()
        this._selection.addRange(range)
        this._range = range
        ffOrIE = this.editor.util.browser.firefox || this.editor.util.browser.msie
        if (!this.editor.inputManager.focused && ffOrIE) {
          this.editor.body.focus()
        }
      } else if (!this._range && this.editor.inputManager.focused && this._selection.rangeCount) {
        this._range = this._selection.getRangeAt(0)
      }
      return this._range
    }

    Selection.prototype.startNodes = function() {
      if (this._range) {
        this._startNodes || (this._startNodes = (function(_this) {
          return function() {
            var startNodes
            startNodes = $(_this._range.startContainer).parentsUntil(_this.editor.body).get()
            startNodes.unshift(_this._range.startContainer)
            return $(startNodes)
          }
        })(this)())
      }
      return this._startNodes
    }

    Selection.prototype.endNodes = function() {
      var endNodes
      if (this._range) {
        this._endNodes || (this._endNodes = this._range.collapsed ? this.startNodes() : (endNodes = $(this._range.endContainer).parentsUntil(this.editor.body).get(), endNodes.unshift(this._range.endContainer), $(endNodes)))
      }
      return this._endNodes
    }

    Selection.prototype.containerNode = function() {
      if (this._range) {
        this._containerNode || (this._containerNode = $(this._range.commonAncestorContainer))
      }
      return this._containerNode
    }

    Selection.prototype.nodes = function() {
      if (this._range) {
        this._nodes || (this._nodes = (function(_this) {
          return function() {
            var nodes
            nodes = []
            if (_this.startNodes().first().is(_this.endNodes().first())) {
              nodes = _this.startNodes().get()
            } else {
              _this.startNodes().each(function(i, node) {
                var $endNode, $node, $nodes, endIndex, index, sharedIndex, startIndex
                $node = $(node)
                if (_this.endNodes().index($node) > -1) {
                  return nodes.push(node)
                } else if ($node.parent().is(_this.editor.body) || (sharedIndex = _this.endNodes().index($node.parent())) > -1) {
                  if (sharedIndex && sharedIndex > -1) {
                    $endNode = _this.endNodes().eq(sharedIndex - 1)
                  } else {
                    $endNode = _this.endNodes().last()
                  }
                  $nodes = $node.parent().contents()
                  startIndex = $nodes.index($node)
                  endIndex = $nodes.index($endNode)
                  return $.merge(nodes, $nodes.slice(startIndex, endIndex).get())
                } else {
                  $nodes = $node.parent().contents()
                  index = $nodes.index($node)
                  return $.merge(nodes, $nodes.slice(index).get())
                }
              })
              _this.endNodes().each(function(i, node) {
                var $node, $nodes, index
                $node = $(node)
                if ($node.parent().is(_this.editor.body) || _this.startNodes().index($node.parent()) > -1) {
                  nodes.push(node)
                  return false
                } else {
                  $nodes = $node.parent().contents()
                  index = $nodes.index($node)
                  return $.merge(nodes, $nodes.slice(0, index + 1))
                }
              })
            }
            return $($.unique(nodes))
          }
        })(this)())
      }
      return this._nodes
    }

    Selection.prototype.blockNodes = function() {
      if (!this._range) {
        return
      }
      this._blockNodes || (this._blockNodes = (function(_this) {
        return function() {
          return _this.nodes().filter(function(i, node) {
            return _this.editor.util.isBlockNode(node)
          })
        }
      })(this)())
      return this._blockNodes
    }

    Selection.prototype.rootNodes = function() {
      if (!this._range) {
        return
      }
      this._rootNodes || (this._rootNodes = (function(_this) {
        return function() {
          return _this.nodes().filter(function(i, node) {
            var $parent
            $parent = $(node).parent()
            return $parent.is(_this.editor.body) || $parent.is('blockquote')
          })
        }
      })(this)())
      return this._rootNodes
    }

    Selection.prototype.rangeAtEndOf = function(node, range) {
      var afterLastNode, beforeLastNode, endNode, endNodeLength, lastNodeIsBr, result
      if (range == null) {
        range = this.range()
      }
      if (!(range && range.collapsed)) {
        return
      }
      node = $(node)[0]
      endNode = range.endContainer
      endNodeLength = this.editor.util.getNodeLength(endNode)
      beforeLastNode = range.endOffset === endNodeLength - 1
      lastNodeIsBr = $(endNode).contents().last().is('br')
      afterLastNode = range.endOffset === endNodeLength
      if (!((beforeLastNode && lastNodeIsBr) || afterLastNode)) {
        return false
      }
      if (node === endNode) {
        return true
      } else if (!$.contains(node, endNode)) {
        return false
      }
      result = true
      $(endNode).parentsUntil(node).addBack().each(function(i, n) {
        var $lastChild, beforeLastbr, isLastNode, nodes
        nodes = $(n).parent().contents().filter(function() {
          return !(this !== n && this.nodeType === 3 && !this.nodeValue)
        })
        $lastChild = nodes.last()
        isLastNode = $lastChild.get(0) === n
        beforeLastbr = $lastChild.is('br') && $lastChild.prev().get(0) === n
        if (!(isLastNode || beforeLastbr)) {
          result = false
          return false
        }
      })
      return result
    }

    Selection.prototype.rangeAtStartOf = function(node, range) {
      var result, startNode
      if (range == null) {
        range = this.range()
      }
      if (!(range && range.collapsed)) {
        return
      }
      node = $(node)[0]
      startNode = range.startContainer
      if (range.startOffset !== 0) {
        return false
      }
      if (node === startNode) {
        return true
      } else if (!$.contains(node, startNode)) {
        return false
      }
      result = true
      $(startNode).parentsUntil(node).addBack().each(function(i, n) {
        var nodes
        nodes = $(n).parent().contents().filter(function() {
          return !(this !== n && this.nodeType === 3 && !this.nodeValue)
        })
        if (nodes.first().get(0) !== n) {
          return result = false
        }
      })
      return result
    }

    Selection.prototype.insertNode = function(node, range) {
      if (range == null) {
        range = this.range()
      }
      if (!range) {
        return
      }
      node = $(node)[0]
      range.insertNode(node)
      return this.setRangeAfter(node, range)
    }

    Selection.prototype.setRangeAfter = function(node, range) {
      if (range == null) {
        range = this.range()
      }
      if (range == null) {
        return
      }
      node = $(node)[0]
      range.setEndAfter(node)
      range.collapse(false)
      return this.range(range)
    }

    Selection.prototype.setRangeBefore = function(node, range) {
      if (range == null) {
        range = this.range()
      }
      if (range == null) {
        return
      }
      node = $(node)[0]
      range.setEndBefore(node)
      range.collapse(false)
      return this.range(range)
    }

    Selection.prototype.setRangeAtStartOf = function(node, range) {
      if (range == null) {
        range = this.range()
      }
      node = $(node).get(0)
      range.setEnd(node, 0)
      range.collapse(false)
      return this.range(range)
    }

    Selection.prototype.setRangeAtEndOf = function(node, range) {
      var $lastNode, $node, contents, lastChild, lastChildLength, lastText, nodeLength
      if (range == null) {
        range = this.range()
      }
      $node = $(node)
      node = $node[0]
      if ($node.is('pre')) {
        contents = $node.contents()
        if (contents.length > 0) {
          lastChild = contents.last()
          lastText = lastChild.text()
          lastChildLength = this.editor.util.getNodeLength(lastChild[0])
          if (lastText.charAt(lastText.length - 1) === '\n') {
            range.setEnd(lastChild[0], lastChildLength - 1)
          } else {
            range.setEnd(lastChild[0], lastChildLength)
          }
        } else {
          range.setEnd(node, 0)
        }
      } else {
        nodeLength = this.editor.util.getNodeLength(node)
        if (node.nodeType !== 3 && nodeLength > 0) {
          $lastNode = $(node).contents().last()
          if ($lastNode.is('br')) {
            nodeLength -= 1
          } else if ($lastNode[0].nodeType !== 3 && this.editor.util.isEmptyNode($lastNode)) {
            $lastNode.append(this.editor.util.phBr)
            node = $lastNode[0]
            nodeLength = 0
          }
        }
        range.setEnd(node, nodeLength)
      }
      range.collapse(false)
      return this.range(range)
    }

    Selection.prototype.deleteRangeContents = function(range) {
      var atEndOfBody, atStartOfBody, endRange, startRange
      if (range == null) {
        range = this.range()
      }
      startRange = range.cloneRange()
      endRange = range.cloneRange()
      startRange.collapse(true)
      endRange.collapse(false)
      atStartOfBody = this.rangeAtStartOf(this.editor.body, startRange)
      atEndOfBody = this.rangeAtEndOf(this.editor.body, endRange)
      if (!range.collapsed && atStartOfBody && atEndOfBody) {
        this.editor.body.empty()
        range.setStart(this.editor.body[0], 0)
        range.collapse(true)
        this.range(range)
      } else {
        range.deleteContents()
      }
      return range
    }

    Selection.prototype.breakBlockEl = function(el, range) {
      var $el
      if (range == null) {
        range = this.range()
      }
      $el = $(el)
      if (!range.collapsed) {
        return $el
      }
      range.setStartBefore($el.get(0))
      if (range.collapsed) {
        return $el
      }
      return $el.before(range.extractContents())
    }

    Selection.prototype.save = function(range) {
      var endCaret, endRange, startCaret
      if (range == null) {
        range = this.range()
      }
      if (this._selectionSaved) {
        return
      }
      endRange = range.cloneRange()
      endRange.collapse(false)
      startCaret = $('<span/>').addClass('simditor-caret-start')
      endCaret = $('<span/>').addClass('simditor-caret-end')
      endRange.insertNode(endCaret[0])
      range.insertNode(startCaret[0])
      this.clear()
      return this._selectionSaved = true
    }

    Selection.prototype.restore = function() {
      var endCaret, endContainer, endOffset, range, startCaret, startContainer, startOffset
      if (!this._selectionSaved) {
        return false
      }
      startCaret = this.editor.body.find('.simditor-caret-start')
      endCaret = this.editor.body.find('.simditor-caret-end')
      if (startCaret.length && endCaret.length) {
        startContainer = startCaret.parent()
        startOffset = startContainer.contents().index(startCaret)
        endContainer = endCaret.parent()
        endOffset = endContainer.contents().index(endCaret)
        if (startContainer[0] === endContainer[0]) {
          endOffset -= 1
        }
        range = document.createRange()
        range.setStart(startContainer.get(0), startOffset)
        range.setEnd(endContainer.get(0), endOffset)
        startCaret.remove()
        endCaret.remove()
        this.range(range)
      } else {
        startCaret.remove()
        endCaret.remove()
      }
      this._selectionSaved = false
      return range
    }

    return Selection
  })(SimpleModule)

  Formatter = (function(superClass) {
    extend(Formatter, superClass)

    function Formatter() {
      return Formatter.__super__.constructor.apply(this, arguments)
    }

    Formatter.pluginName = 'Formatter'

    Formatter.prototype.opts = {
      allowedTags: [],
      allowedAttributes: {},
      allowedStyles: {}
    }

    Formatter.prototype._init = function() {
      this.editor = this._module
      this._allowedTags = $.merge(['br', 'span', 'a', 'img', 'b', 'strong', 'i', 'strike', 'u', 'font', 'p', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'h1', 'h2', 'h3', 'h4', 'hr'], this.opts.allowedTags)
      this._allowedAttributes = $.extend({
        img: ['src', 'alt', 'width', 'height', 'data-non-image'],
        a: ['href', 'target'],
        font: ['color'],
        code: ['class']
      }, this.opts.allowedAttributes)
      this._allowedStyles = $.extend({
        span: ['color', 'font-size'],
        b: ['color'],
        i: ['color'],
        strong: ['color'],
        strike: ['color'],
        u: ['color'],
        p: ['margin-left', 'text-align'],
        h1: ['margin-left', 'text-align'],
        h2: ['margin-left', 'text-align'],
        h3: ['margin-left', 'text-align'],
        h4: ['margin-left', 'text-align']
      }, this.opts.allowedStyles)
      return this.editor.body.on('click', 'a', function(e) {
        return false
      })
    }

    Formatter.prototype.decorate = function($el) {
      if ($el == null) {
        $el = this.editor.body
      }
      this.editor.trigger('decorate', [$el])
      return $el
    }

    Formatter.prototype.undecorate = function($el) {
      if ($el == null) {
        $el = this.editor.body.clone()
      }
      this.editor.trigger('undecorate', [$el])
      return $el
    }

    Formatter.prototype.autolink = function($el) {
      var $link, $node, findLinkNode, k, lastIndex, len, linkNodes, match, re, replaceEls, subStr, text, uri
      if ($el == null) {
        $el = this.editor.body
      }
      linkNodes = []
      findLinkNode = function($parentNode) {
        return $parentNode.contents().each(function(i, node) {
          var $node, text
          $node = $(node)
          if ($node.is('a') || $node.closest('a, pre', $el).length) {
            return
          }
          if (!$node.is('iframe') && $node.contents().length) {
            return findLinkNode($node)
          } else if ((text = $node.text()) && /https?:\/\/|www\./ig.test(text)) {
            return linkNodes.push($node)
          }
        })
      }
      findLinkNode($el)
      re = /(https?:\/\/|www\.)[\w\-\.\?&=\/#%:,@\!\+]+/ig
      for (k = 0, len = linkNodes.length; k < len; k++) {
        $node = linkNodes[k]
        text = $node.text()
        replaceEls = []
        match = null
        lastIndex = 0
        while ((match = re.exec(text)) !== null) {
          subStr = text.substring(lastIndex, match.index)
          replaceEls.push(document.createTextNode(subStr))
          lastIndex = re.lastIndex
          uri = /^(http(s)?:\/\/|\/)/.test(match[0]) ? match[0] : 'http://' + match[0]
          $link = $('<a href="' + uri + '" rel="nofollow"></a>').text(match[0])
          replaceEls.push($link[0])
        }
        replaceEls.push(document.createTextNode(text.substring(lastIndex)))
        $node.replaceWith($(replaceEls))
      }
      return $el
    }

    Formatter.prototype.format = function($el) {
      var $node, blockNode, k, l, len, len1, n, node, ref, ref1
      if ($el == null) {
        $el = this.editor.body
      }
      if ($el.is(':empty')) {
        $el.append('<p>' + this.editor.util.phBr + '</p>')
        return $el
      }
      ref = $el.contents()
      for (k = 0, len = ref.length; k < len; k++) {
        n = ref[k]
        this.cleanNode(n, true)
      }
      ref1 = $el.contents()
      for (l = 0, len1 = ref1.length; l < len1; l++) {
        node = ref1[l]
        $node = $(node)
        if ($node.is('br')) {
          if (typeof blockNode !== 'undefined' && blockNode !== null) {
            blockNode = null
          }
          $node.remove()
        } else if (this.editor.util.isBlockNode(node)) {
          if ($node.is('li')) {
            if (blockNode && blockNode.is('ul, ol')) {
              blockNode.append(node)
            } else {
              blockNode = $('<ul/>').insertBefore(node)
              blockNode.append(node)
            }
          } else {
            blockNode = null
          }
        } else {
          if (!blockNode || blockNode.is('ul, ol')) {
            blockNode = $('<p/>').insertBefore(node)
          }
          blockNode.append(node)
          if (this.editor.util.isEmptyNode(blockNode)) {
            blockNode.append(this.editor.util.phBr)
          }
        }
      }
      return $el
    }

    Formatter.prototype.cleanNode = function(node, recursive) {
      var $blockEls, $childImg, $node, $p, $td, allowedAttributes, attr, contents, isDecoration, k, l, len, len1, n, ref, ref1, text, textNode
      $node = $(node)
      if (!($node.length > 0)) {
        return
      }
      if ($node[0].nodeType === 3) {
        text = $node.text().replace(/(\r\n|\n|\r)/gm, '')
        if (text) {
          textNode = document.createTextNode(text)
          $node.replaceWith(textNode)
        } else {
          $node.remove()
        }
        return
      }
      contents = $node.is('iframe') ? null : $node.contents()
      isDecoration = this.editor.util.isDecoratedNode($node)
      if ($node.is(this._allowedTags.join(',')) || isDecoration) {
        if ($node.is('a') && ($childImg = $node.find('img')).length > 0) {
          $node.replaceWith($childImg)
          $node = $childImg
          contents = null
        }
        if ($node.is('td') && ($blockEls = $node.find(this.editor.util.blockNodes.join(','))).length > 0) {
          $blockEls.each((function(_this) {
            return function(i, blockEl) {
              return $(blockEl).contents().unwrap()
            }
          })(this))
          contents = $node.contents()
        }
        if ($node.is('img') && $node.hasClass('uploading')) {
          $node.remove()
        }
        if (!isDecoration) {
          allowedAttributes = this._allowedAttributes[$node[0].tagName.toLowerCase()]
          ref = $.makeArray($node[0].attributes)
          for (k = 0, len = ref.length; k < len; k++) {
            attr = ref[k]
            if (attr.name === 'style') {
              continue
            }
            if (!((allowedAttributes != null) && (ref1 = attr.name, indexOf.call(allowedAttributes, ref1) >= 0))) {
              $node.removeAttr(attr.name)
            }
          }
          this._cleanNodeStyles($node)
          if ($node.is('span') && $node[0].attributes.length === 0) {
            $node.contents().first().unwrap()
          }
        }
      } else if ($node[0].nodeType === 1 && !$node.is(':empty')) {
        if ($node.is('div, article, dl, header, footer, tr')) {
          $node.append('<br/>')
          contents.first().unwrap()
        } else if ($node.is('table')) {
          $p = $('<p/>')
          $node.find('tr').each(function(i, tr) {
            return $p.append($(tr).text() + '<br/>')
          })
          $node.replaceWith($p)
          contents = null
        } else if ($node.is('thead, tfoot')) {
          $node.remove()
          contents = null
        } else if ($node.is('th')) {
          $td = $('<td/>').append($node.contents())
          $node.replaceWith($td)
        } else {
          contents.first().unwrap()
        }
      } else {
        $node.remove()
        contents = null
      }
      if (recursive && (contents != null) && !$node.is('pre')) {
        for (l = 0, len1 = contents.length; l < len1; l++) {
          n = contents[l]
          this.cleanNode(n, true)
        }
      }
      return null
    }

    Formatter.prototype._cleanNodeStyles = function($node) {
      var allowedStyles, k, len, pair, ref, ref1, style, styleStr, styles
      styleStr = $node.attr('style')
      if (!styleStr) {
        return
      }
      $node.removeAttr('style')
      allowedStyles = this._allowedStyles[$node[0].tagName.toLowerCase()]
      if (!(allowedStyles && allowedStyles.length > 0)) {
        return $node
      }
      styles = {}
      ref = styleStr.split(';')
      for (k = 0, len = ref.length; k < len; k++) {
        style = ref[k]
        style = $.trim(style)
        pair = style.split(':')
        if (!(pair.length = 2)) {
          continue
        }
        if (ref1 = pair[0], indexOf.call(allowedStyles, ref1) >= 0) {
          styles[$.trim(pair[0])] = $.trim(pair[1])
        }
      }
      if (Object.keys(styles).length > 0) {
        $node.css(styles)
      }
      return $node
    }

    Formatter.prototype.clearHtml = function(html, lineBreak) {
      var container, contents, result
      if (lineBreak == null) {
        lineBreak = true
      }
      container = $('<div/>').append(html)
      contents = container.contents()
      result = ''
      contents.each((function(_this) {
        return function(i, node) {
          var $node, children
          if (node.nodeType === 3) {
            return result += node.nodeValue
          } else if (node.nodeType === 1) {
            $node = $(node)
            children = $node.is('iframe') ? null : $node.contents()
            if (children && children.length > 0) {
              result += _this.clearHtml(children)
            }
            if (lineBreak && i < contents.length - 1 && $node.is('br, p, div, li,tr, pre, address, artticle, aside, dl, figcaption, footer, h1, h2,h3, h4, header')) {
              return result += '\n'
            }
          }
        }
      })(this))
      return result
    }

    Formatter.prototype.beautify = function($contents) {
      var uselessP
      uselessP = function($el) {
        return !!($el.is('p') && !$el.text() && $el.children(':not(br)').length < 1)
      }
      return $contents.each(function(i, el) {
        var $el, invalid
        $el = $(el)
        invalid = $el.is(':not(img, br, col, td, hr, [class^="simditor-"]):empty')
        if (invalid || uselessP($el)) {
          $el.remove()
        }
        return $el.find(':not(img, br, col, td, hr, [class^="simditor-"]):empty').remove()
      })
    }

    return Formatter
  })(SimpleModule)

  InputManager = (function(superClass) {
    extend(InputManager, superClass)

    function InputManager() {
      return InputManager.__super__.constructor.apply(this, arguments)
    }

    InputManager.pluginName = 'InputManager'

    InputManager.prototype._modifierKeys = [16, 17, 18, 91, 93, 224]

    InputManager.prototype._arrowKeys = [37, 38, 39, 40]

    InputManager.prototype._init = function() {
      var selectAllKey, submitKey
      this.editor = this._module
      this.throttledValueChanged = this.editor.util.throttle((function(_this) {
        return function(params) {
          return setTimeout(function() {
            return _this.editor.trigger('valuechanged', params)
          }, 10)
        }
      })(this), 300)
      this.throttledSelectionChanged = this.editor.util.throttle((function(_this) {
        return function() {
          return _this.editor.trigger('selectionchanged')
        }
      })(this), 50)
      $(document).on('selectionchange.simditor' + this.editor.id, (function(_this) {
        return function(e) {
          var triggerEvent
          if (!(_this.focused && !_this.editor.clipboard.pasting)) {
            return
          }
          triggerEvent = function() {
            if (_this._selectionTimer) {
              clearTimeout(_this._selectionTimer)
              _this._selectionTimer = null
            }
            if (_this.editor.selection._selection.rangeCount > 0) {
              return _this.throttledSelectionChanged()
            } else {
              return _this._selectionTimer = setTimeout(function() {
                _this._selectionTimer = null
                if (_this.focused) {
                  return triggerEvent()
                }
              }, 10)
            }
          }
          return triggerEvent()
        }
      })(this))
      this.editor.on('valuechanged', (function(_this) {
        return function() {
          var $rootBlocks
          _this.lastCaretPosition = null
          $rootBlocks = _this.editor.body.children().filter(function(i, node) {
            return _this.editor.util.isBlockNode(node)
          })
          if (_this.focused && $rootBlocks.length === 0) {
            _this.editor.selection.save()
            _this.editor.formatter.format()
            _this.editor.selection.restore()
          }
          _this.editor.body.find('hr, pre, .simditor-table').each(function(i, el) {
            var $el, formatted
            $el = $(el)
            if ($el.parent().is('blockquote') || $el.parent()[0] === _this.editor.body[0]) {
              formatted = false
              if ($el.next().length === 0) {
                $('<p/>').append(_this.editor.util.phBr).insertAfter($el)
                formatted = true
              }
              if ($el.prev().length === 0) {
                $('<p/>').append(_this.editor.util.phBr).insertBefore($el)
                formatted = true
              }
              if (formatted) {
                return _this.throttledValueChanged()
              }
            }
          })
          _this.editor.body.find('pre:empty').append(_this.editor.util.phBr)
          if (!_this.editor.util.support.onselectionchange && _this.focused) {
            return _this.throttledSelectionChanged()
          }
        }
      })(this))
      this.editor.body.on('keydown', $.proxy(this._onKeyDown, this)).on('keypress', $.proxy(this._onKeyPress, this)).on('keyup', $.proxy(this._onKeyUp, this)).on('mouseup', $.proxy(this._onMouseUp, this)).on('focus', $.proxy(this._onFocus, this)).on('blur', $.proxy(this._onBlur, this)).on('drop', $.proxy(this._onDrop, this)).on('input', $.proxy(this._onInput, this))
      if (this.editor.util.browser.firefox) {
        this.editor.hotkeys.add('cmd+left', (function(_this) {
          return function(e) {
            e.preventDefault()
            _this.editor.selection._selection.modify('move', 'backward', 'lineboundary')
            return false
          }
        })(this))
        this.editor.hotkeys.add('cmd+right', (function(_this) {
          return function(e) {
            e.preventDefault()
            _this.editor.selection._selection.modify('move', 'forward', 'lineboundary')
            return false
          }
        })(this))
        selectAllKey = this.editor.util.os.mac ? 'cmd+a' : 'ctrl+a'
        this.editor.hotkeys.add(selectAllKey, (function(_this) {
          return function(e) {
            var $children, firstBlock, lastBlock, range
            $children = _this.editor.body.children()
            if (!($children.length > 0)) {
              return
            }
            firstBlock = $children.first().get(0)
            lastBlock = $children.last().get(0)
            range = document.createRange()
            range.setStart(firstBlock, 0)
            range.setEnd(lastBlock, _this.editor.util.getNodeLength(lastBlock))
            _this.editor.selection.range(range)
            return false
          }
        })(this))
      }
      submitKey = this.editor.util.os.mac ? 'cmd+enter' : 'ctrl+enter'
      return this.editor.hotkeys.add(submitKey, (function(_this) {
        return function(e) {
          _this.editor.el.closest('form').find('button:submit').click()
          return false
        }
      })(this))
    }

    InputManager.prototype._onFocus = function(e) {
      if (this.editor.clipboard.pasting) {
        return
      }
      this.editor.el.addClass('focus').removeClass('error')
      this.focused = true
      return setTimeout((function(_this) {
        return function() {
          var $blockEl, range
          range = _this.editor.selection._selection.getRangeAt(0)
          if (range.startContainer === _this.editor.body[0]) {
            if (_this.lastCaretPosition) {
              _this.editor.undoManager.caretPosition(_this.lastCaretPosition)
            } else {
              $blockEl = _this.editor.body.children().first()
              range = document.createRange()
              _this.editor.selection.setRangeAtStartOf($blockEl, range)
            }
          }
          _this.lastCaretPosition = null
          _this.editor.triggerHandler('focus')
          if (!_this.editor.util.support.onselectionchange) {
            return _this.throttledSelectionChanged()
          }
        }
      })(this), 0)
    }

    InputManager.prototype._onBlur = function(e) {
      var ref
      if (this.editor.clipboard.pasting) {
        return
      }
      this.editor.el.removeClass('focus')
      this.editor.sync()
      this.focused = false
      this.lastCaretPosition = (ref = this.editor.undoManager.currentState()) != null ? ref.caret : void 0
      return this.editor.triggerHandler('blur')
    }

    InputManager.prototype._onMouseUp = function(e) {
      if (!this.editor.util.support.onselectionchange) {
        return this.throttledSelectionChanged()
      }
    }

    InputManager.prototype._onKeyDown = function(e) {
      var ref, ref1
      if (this.editor.triggerHandler(e) === false) {
        return false
      }
      if (this.editor.hotkeys.respondTo(e)) {
        return
      }
      if (this.editor.keystroke.respondTo(e)) {
        this.throttledValueChanged()
        return false
      }
      if ((ref = e.which, indexOf.call(this._modifierKeys, ref) >= 0) || (ref1 = e.which, indexOf.call(this._arrowKeys, ref1) >= 0)) {
        return
      }
      if (this.editor.util.metaKey(e) && e.which === 86) {
        return
      }
      if (!this.editor.util.support.oninput) {
        this.throttledValueChanged(['typing'])
      }
      return null
    }

    InputManager.prototype._onKeyPress = function(e) {
      if (this.editor.triggerHandler(e) === false) {
        return false
      }
    }

    InputManager.prototype._onKeyUp = function(e) {
      var p, ref
      if (this.editor.triggerHandler(e) === false) {
        return false
      }
      if (!this.editor.util.support.onselectionchange && (ref = e.which, indexOf.call(this._arrowKeys, ref) >= 0)) {
        this.throttledValueChanged()
        return
      }
      if ((e.which === 8 || e.which === 46) && this.editor.util.isEmptyNode(this.editor.body)) {
        this.editor.body.empty()
        p = $('<p/>').append(this.editor.util.phBr).appendTo(this.editor.body)
        this.editor.selection.setRangeAtStartOf(p)
      }
    }

    InputManager.prototype._onDrop = function(e) {
      if (this.editor.triggerHandler(e) === false) {
        return false
      }
      return this.throttledValueChanged()
    }

    InputManager.prototype._onInput = function(e) {
      return this.throttledValueChanged(['oninput'])
    }

    return InputManager
  })(SimpleModule)

  Keystroke = (function(superClass) {
    extend(Keystroke, superClass)

    function Keystroke() {
      return Keystroke.__super__.constructor.apply(this, arguments)
    }

    Keystroke.pluginName = 'Keystroke'

    Keystroke.prototype._init = function() {
      this.editor = this._module
      this._keystrokeHandlers = {}
      return this._initKeystrokeHandlers()
    }

    Keystroke.prototype.add = function(key, node, handler) {
      key = key.toLowerCase()
      key = this.editor.hotkeys.constructor.aliases[key] || key
      if (!this._keystrokeHandlers[key]) {
        this._keystrokeHandlers[key] = {}
      }
      return this._keystrokeHandlers[key][node] = handler
    }

    Keystroke.prototype.respondTo = function(e) {
      var base, key, ref, result
      key = (ref = this.editor.hotkeys.constructor.keyNameMap[e.which]) != null ? ref.toLowerCase() : void 0
      if (!key) {
        return
      }
      if (key in this._keystrokeHandlers) {
        result = typeof (base = this._keystrokeHandlers[key])['*'] === 'function' ? base['*'](e) : void 0
        if (!result) {
          this.editor.selection.startNodes().each((function(_this) {
            return function(i, node) {
              var handler, ref1
              if (node.nodeType !== Node.ELEMENT_NODE) {
                return
              }
              handler = (ref1 = _this._keystrokeHandlers[key]) != null ? ref1[node.tagName.toLowerCase()] : void 0
              result = typeof handler === 'function' ? handler(e, $(node)) : void 0
              if (result === true || result === false) {
                return false
              }
            }
          })(this))
        }
        if (result) {
          return true
        }
      }
    }

    Keystroke.prototype._initKeystrokeHandlers = function() {
      var titleEnterHandler
      if (this.editor.util.browser.safari) {
        this.add('enter', '*', (function(_this) {
          return function(e) {
            var $blockEl, $br
            if (!e.shiftKey) {
              return
            }
            $blockEl = _this.editor.selection.blockNodes().last()
            if ($blockEl.is('pre')) {
              return
            }
            $br = $('<br/>')
            if (_this.editor.selection.rangeAtEndOf($blockEl)) {
              _this.editor.selection.insertNode($br)
              _this.editor.selection.insertNode($('<br/>'))
              _this.editor.selection.setRangeBefore($br)
            } else {
              _this.editor.selection.insertNode($br)
            }
            return true
          }
        })(this))
      }
      if (this.editor.util.browser.webkit || this.editor.util.browser.msie) {
        titleEnterHandler = (function(_this) {
          return function(e, $node) {
            var $p
            if (!_this.editor.selection.rangeAtEndOf($node)) {
              return
            }
            $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node)
            _this.editor.selection.setRangeAtStartOf($p)
            return true
          }
        })(this)
        this.add('enter', 'h1', titleEnterHandler)
        this.add('enter', 'h2', titleEnterHandler)
        this.add('enter', 'h3', titleEnterHandler)
        this.add('enter', 'h4', titleEnterHandler)
        this.add('enter', 'h5', titleEnterHandler)
        this.add('enter', 'h6', titleEnterHandler)
      }
      this.add('backspace', '*', (function(_this) {
        return function(e) {
          var $blockEl, $prevBlockEl, $rootBlock, isWebkit
          $rootBlock = _this.editor.selection.rootNodes().first()
          $prevBlockEl = $rootBlock.prev()
          if ($prevBlockEl.is('hr') && _this.editor.selection.rangeAtStartOf($rootBlock)) {
            _this.editor.selection.save()
            $prevBlockEl.remove()
            _this.editor.selection.restore()
            return true
          }
          $blockEl = _this.editor.selection.blockNodes().last()
          isWebkit = _this.editor.util.browser.webkit
          if (isWebkit && _this.editor.selection.rangeAtStartOf($blockEl)) {
            _this.editor.selection.save()
            _this.editor.formatter.cleanNode($blockEl, true)
            _this.editor.selection.restore()
            return null
          }
        }
      })(this))
      this.add('enter', 'li', (function(_this) {
        return function(e, $node) {
          var $cloneNode, listEl, newBlockEl, newListEl
          $cloneNode = $node.clone()
          $cloneNode.find('ul, ol').remove()
          if (!(_this.editor.util.isEmptyNode($cloneNode) && $node.is(_this.editor.selection.blockNodes().last()))) {
            return
          }
          listEl = $node.parent()
          if ($node.next('li').length > 0) {
            if (!_this.editor.util.isEmptyNode($node)) {
              return
            }
            if (listEl.parent('li').length > 0) {
              newBlockEl = $('<li/>').append(_this.editor.util.phBr).insertAfter(listEl.parent('li'))
              newListEl = $('<' + listEl[0].tagName + '/>').append($node.nextAll('li'))
              newBlockEl.append(newListEl)
            } else {
              newBlockEl = $('<p/>').append(_this.editor.util.phBr).insertAfter(listEl)
              newListEl = $('<' + listEl[0].tagName + '/>').append($node.nextAll('li'))
              newBlockEl.after(newListEl)
            }
          } else {
            if (listEl.parent('li').length > 0) {
              newBlockEl = $('<li/>').insertAfter(listEl.parent('li'))
              if ($node.contents().length > 0) {
                newBlockEl.append($node.contents())
              } else {
                newBlockEl.append(_this.editor.util.phBr)
              }
            } else {
              newBlockEl = $('<p/>').append(_this.editor.util.phBr).insertAfter(listEl)
              if ($node.children('ul, ol').length > 0) {
                newBlockEl.after($node.children('ul, ol'))
              }
            }
          }
          if ($node.prev('li').length) {
            $node.remove()
          } else {
            listEl.remove()
          }
          _this.editor.selection.setRangeAtStartOf(newBlockEl)
          return true
        }
      })(this))
      this.add('enter', 'pre', (function(_this) {
        return function(e, $node) {
          var $p, breakNode, range
          e.preventDefault()
          if (e.shiftKey) {
            $p = $('<p/>').append(_this.editor.util.phBr).insertAfter($node)
            _this.editor.selection.setRangeAtStartOf($p)
            return true
          }
          range = _this.editor.selection.range()
          breakNode = null
          range.deleteContents()
          if (!_this.editor.util.browser.msie && _this.editor.selection.rangeAtEndOf($node)) {
            breakNode = document.createTextNode('\n\n')
            range.insertNode(breakNode)
            range.setEnd(breakNode, 1)
          } else {
            breakNode = document.createTextNode('\n')
            range.insertNode(breakNode)
            range.setStartAfter(breakNode)
          }
          range.collapse(false)
          _this.editor.selection.range(range)
          return true
        }
      })(this))
      this.add('enter', 'blockquote', (function(_this) {
        return function(e, $node) {
          var $closestBlock, range
          $closestBlock = _this.editor.selection.blockNodes().last()
          if (!($closestBlock.is('p') && !$closestBlock.next().length && _this.editor.util.isEmptyNode($closestBlock))) {
            return
          }
          $node.after($closestBlock)
          range = document.createRange()
          _this.editor.selection.setRangeAtStartOf($closestBlock, range)
          return true
        }
      })(this))
      this.add('backspace', 'li', (function(_this) {
        return function(e, $node) {
          var $br, $childList, $newLi, $prevChildList, $prevNode, $textNode, isFF, range, text
          $childList = $node.children('ul, ol')
          $prevNode = $node.prev('li')
          if (!($childList.length > 0 && $prevNode.length > 0)) {
            return false
          }
          text = ''
          $textNode = null
          $node.contents().each(function(i, n) {
            if (n.nodeType === 1 && /UL|OL/.test(n.nodeName)) {
              return false
            }
            if (n.nodeType === 1 && /BR/.test(n.nodeName)) {
              return
            }
            if (n.nodeType === 3 && n.nodeValue) {
              text += n.nodeValue
            } else if (n.nodeType === 1) {
              text += $(n).text()
            }
            return $textNode = $(n)
          })
          isFF = _this.editor.util.browser.firefox && !$textNode.next('br').length
          if ($textNode && text.length === 1 && isFF) {
            $br = $(_this.editor.util.phBr).insertAfter($textNode)
            $textNode.remove()
            _this.editor.selection.setRangeBefore($br)
            return true
          } else if (text.length > 0) {
            return false
          }
          range = document.createRange()
          $prevChildList = $prevNode.children('ul, ol')
          if ($prevChildList.length > 0) {
            $newLi = $('<li/>').append(_this.editor.util.phBr).appendTo($prevChildList)
            $prevChildList.append($childList.children('li'))
            $node.remove()
            _this.editor.selection.setRangeAtEndOf($newLi, range)
          } else {
            _this.editor.selection.setRangeAtEndOf($prevNode, range)
            $prevNode.append($childList)
            $node.remove()
            _this.editor.selection.range(range)
          }
          return true
        }
      })(this))
      this.add('backspace', 'pre', (function(_this) {
        return function(e, $node) {
          var $newNode, codeStr, range
          if (!_this.editor.selection.rangeAtStartOf($node)) {
            return
          }
          codeStr = $node.html().replace('\n', '<br/>') || _this.editor.util.phBr
          $newNode = $('<p/>').append(codeStr).insertAfter($node)
          $node.remove()
          range = document.createRange()
          _this.editor.selection.setRangeAtStartOf($newNode, range)
          return true
        }
      })(this))
      return this.add('backspace', 'blockquote', (function(_this) {
        return function(e, $node) {
          var $firstChild, range
          if (!_this.editor.selection.rangeAtStartOf($node)) {
            return
          }
          $firstChild = $node.children().first().unwrap()
          range = document.createRange()
          _this.editor.selection.setRangeAtStartOf($firstChild, range)
          return true
        }
      })(this))
    }

    return Keystroke
  })(SimpleModule)

  UndoManager = (function(superClass) {
    extend(UndoManager, superClass)

    function UndoManager() {
      return UndoManager.__super__.constructor.apply(this, arguments)
    }

    UndoManager.pluginName = 'UndoManager'

    UndoManager.prototype._index = -1

    UndoManager.prototype._capacity = 20

    UndoManager.prototype._startPosition = null

    UndoManager.prototype._endPosition = null

    UndoManager.prototype._init = function() {
      var redoShortcut, undoShortcut
      this.editor = this._module
      this._stack = []
      if (this.editor.util.os.mac) {
        undoShortcut = 'cmd+z'
        redoShortcut = 'shift+cmd+z'
      } else if (this.editor.util.os.win) {
        undoShortcut = 'ctrl+z'
        redoShortcut = 'ctrl+y'
      } else {
        undoShortcut = 'ctrl+z'
        redoShortcut = 'shift+ctrl+z'
      }
      this.editor.hotkeys.add(undoShortcut, (function(_this) {
        return function(e) {
          e.preventDefault()
          _this.undo()
          return false
        }
      })(this))
      this.editor.hotkeys.add(redoShortcut, (function(_this) {
        return function(e) {
          e.preventDefault()
          _this.redo()
          return false
        }
      })(this))
      this.throttledPushState = this.editor.util.throttle((function(_this) {
        return function() {
          return _this._pushUndoState()
        }
      })(this), 2000)
      this.editor.on('valuechanged', (function(_this) {
        return function(e, src) {
          if (src === 'undo' || src === 'redo') {
            return
          }
          return _this.throttledPushState()
        }
      })(this))
      this.editor.on('selectionchanged', (function(_this) {
        return function(e) {
          _this.resetCaretPosition()
          return _this.update()
        }
      })(this))
      this.editor.on('focus', (function(_this) {
        return function(e) {
          if (_this._stack.length === 0) {
            return _this._pushUndoState()
          }
        }
      })(this))
      return this.editor.on('blur', (function(_this) {
        return function(e) {
          return _this.resetCaretPosition()
        }
      })(this))
    }

    UndoManager.prototype.resetCaretPosition = function() {
      this._startPosition = null
      return this._endPosition = null
    }

    UndoManager.prototype.startPosition = function() {
      if (this.editor.selection._range) {
        this._startPosition || (this._startPosition = this._getPosition('start'))
      }
      return this._startPosition
    }

    UndoManager.prototype.endPosition = function() {
      if (this.editor.selection._range) {
        this._endPosition || (this._endPosition = (function(_this) {
          return function() {
            var range
            range = _this.editor.selection.range()
            if (range.collapsed) {
              return _this._startPosition
            }
            return _this._getPosition('end')
          }
        })(this)())
      }
      return this._endPosition
    }

    UndoManager.prototype._pushUndoState = function() {
      var caret
      if (this.editor.triggerHandler('pushundostate') === false) {
        return
      }
      caret = this.caretPosition()
      if (!caret.start) {
        return
      }
      this._index += 1
      this._stack.length = this._index
      this._stack.push({
        html: this.editor.body.html(),
        caret: this.caretPosition()
      })
      if (this._stack.length > this._capacity) {
        this._stack.shift()
        return this._index -= 1
      }
    }

    UndoManager.prototype.currentState = function() {
      if (this._stack.length && this._index > -1) {
        return this._stack[this._index]
      } else {
        return null
      }
    }

    UndoManager.prototype.undo = function() {
      var state
      if (this._index < 1 || this._stack.length < 2) {
        return
      }
      this.editor.hidePopover()
      this._index -= 1
      state = this._stack[this._index]
      this.editor.body.get(0).innerHTML = state.html
      this.caretPosition(state.caret)
      this.editor.body.find('.selected').removeClass('selected')
      this.editor.sync()
      return this.editor.trigger('valuechanged', ['undo'])
    }

    UndoManager.prototype.redo = function() {
      var state
      if (this._index < 0 || this._stack.length < this._index + 2) {
        return
      }
      this.editor.hidePopover()
      this._index += 1
      state = this._stack[this._index]
      this.editor.body.get(0).innerHTML = state.html
      this.caretPosition(state.caret)
      this.editor.body.find('.selected').removeClass('selected')
      this.editor.sync()
      return this.editor.trigger('valuechanged', ['redo'])
    }

    UndoManager.prototype.update = function() {
      var currentState
      currentState = this.currentState()
      if (!currentState) {
        return
      }
      currentState.html = this.editor.body.html()
      return currentState.caret = this.caretPosition()
    }

    UndoManager.prototype._getNodeOffset = function(node, index) {
      var $parent, merging, offset
      if ($.isNumeric(index)) {
        $parent = $(node)
      } else {
        $parent = $(node).parent()
      }
      offset = 0
      merging = false
      $parent.contents().each(function(i, child) {
        if (node === child || (index === i && i === 0)) {
          return false
        }
        if (child.nodeType === Node.TEXT_NODE) {
          if (!merging && child.nodeValue.length > 0) {
            offset += 1
            merging = true
          }
        } else {
          offset += 1
          merging = false
        }
        if (index - 1 === i) {
          return false
        }
        return null
      })
      return offset
    }

    UndoManager.prototype._getPosition = function(type) {
      var $nodes, node, nodes, offset, position, prevNode, range
      if (type == null) {
        type = 'start'
      }
      range = this.editor.selection.range()
      offset = range[type + 'Offset']
      $nodes = this.editor.selection[type + 'Nodes']()
      node = $nodes.first()[0]
      if (node.nodeType === Node.TEXT_NODE) {
        prevNode = node.previousSibling
        while (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
          node = prevNode
          offset += this.editor.util.getNodeLength(prevNode)
          prevNode = prevNode.previousSibling
        }
        nodes = $nodes.get()
        nodes[0] = node
        $nodes = $(nodes)
      } else {
        offset = this._getNodeOffset(node, offset)
      }
      position = [offset]
      $nodes.each((function(_this) {
        return function(i, node) {
          return position.unshift(_this._getNodeOffset(node))
        }
      })(this))
      return position
    }

    UndoManager.prototype._getNodeByPosition = function(position) {
      var child, childNodes, i, k, len, node, offset, ref
      node = this.editor.body[0]
      ref = position.slice(0, position.length - 1)
      for (i = k = 0, len = ref.length; k < len; i = ++k) {
        offset = ref[i]
        childNodes = node.childNodes
        if (offset > childNodes.length - 1) {
          if (i === position.length - 2 && $(node).is('pre:empty')) {
            child = document.createTextNode('')
            node.appendChild(child)
            childNodes = node.childNodes
          } else {
            node = null
            break
          }
        }
        node = childNodes[offset]
      }
      return node
    }

    UndoManager.prototype.caretPosition = function(caret) {
      var endContainer, endOffset, range, startContainer, startOffset
      if (!caret) {
        range = this.editor.selection.range()
        caret = this.editor.inputManager.focused && (range != null) ? {
          start: this.startPosition(),
          end: this.endPosition(),
          collapsed: range.collapsed
        } : {}
        return caret
      } else {
        if (!caret.start) {
          return
        }
        startContainer = this._getNodeByPosition(caret.start)
        startOffset = caret.start[caret.start.length - 1]
        if (caret.collapsed) {
          endContainer = startContainer
          endOffset = startOffset
        } else {
          endContainer = this._getNodeByPosition(caret.end)
          endOffset = caret.start[caret.start.length - 1]
        }
        if (!startContainer || !endContainer) {
          if (typeof console !== 'undefined' && console !== null) {
            if (typeof console.warn === 'function') {
              console.warn('simditor: invalid caret state')
            }
          }
          return
        }
        range = document.createRange()
        range.setStart(startContainer, startOffset)
        range.setEnd(endContainer, endOffset)
        return this.editor.selection.range(range)
      }
    }

    return UndoManager
  })(SimpleModule)

  Util = (function(superClass) {
    extend(Util, superClass)

    function Util() {
      return Util.__super__.constructor.apply(this, arguments)
    }

    Util.pluginName = 'Util'

    Util.prototype._init = function() {
      this.editor = this._module
      if (this.browser.msie && this.browser.version < 11) {
        return this.phBr = ''
      }
    }

    Util.prototype.phBr = '<br/>'

    Util.prototype.os = (function() {
      var os
      os = {}
      if (/Mac/.test(navigator.appVersion)) {
        os.mac = true
      } else if (/Linux/.test(navigator.appVersion)) {
        os.linux = true
      } else if (/Win/.test(navigator.appVersion)) {
        os.win = true
      } else if (/X11/.test(navigator.appVersion)) {
        os.unix = true
      }
      if (/Mobi/.test(navigator.appVersion)) {
        os.mobile = true
      }
      return os
    })()

    Util.prototype.browser = (function() {
      var chrome, edge, firefox, ie, ref, ref1, ref2, ref3, ref4, safari, ua
      ua = navigator.userAgent
      ie = /(msie|trident)/i.test(ua)
      chrome = /chrome|crios/i.test(ua)
      safari = /safari/i.test(ua) && !chrome
      firefox = /firefox/i.test(ua)
      edge = /edge/i.test(ua)
      if (ie) {
        return {
          msie: true,
          version: ((ref = ua.match(/(msie |rv:)(\d+(\.\d+)?)/i)) != null ? ref[2] : void 0) * 1
        }
      } else if (edge) {
        return {
          edge: true,
          webkit: true,
          version: ((ref1 = ua.match(/edge\/(\d+(\.\d+)?)/i)) != null ? ref1[1] : void 0) * 1
        }
      } else if (chrome) {
        return {
          webkit: true,
          chrome: true,
          version: ((ref2 = ua.match(/(?:chrome|crios)\/(\d+(\.\d+)?)/i)) != null ? ref2[1] : void 0) * 1
        }
      } else if (safari) {
        return {
          webkit: true,
          safari: true,
          version: ((ref3 = ua.match(/version\/(\d+(\.\d+)?)/i)) != null ? ref3[1] : void 0) * 1
        }
      } else if (firefox) {
        return {
          mozilla: true,
          firefox: true,
          version: ((ref4 = ua.match(/firefox\/(\d+(\.\d+)?)/i)) != null ? ref4[1] : void 0) * 1
        }
      } else {
        return {}
      }
    })()

    Util.prototype.support = (function() {
      return {
        onselectionchange: (function() {
          var e, onselectionchange
          onselectionchange = document.onselectionchange
          if (onselectionchange !== void 0) {
            try {
              document.onselectionchange = 0
              return document.onselectionchange === null
            } catch (_error) {
              e = _error
            } finally {
              document.onselectionchange = onselectionchange
            }
          }
          return false
        })(),
        oninput: (function() {
          return !/(msie|trident)/i.test(navigator.userAgent)
        })()
      }
    })()

    Util.prototype.reflow = function(el) {
      if (el == null) {
        el = document
      }
      return $(el)[0].offsetHeight
    }

    Util.prototype.metaKey = function(e) {
      var isMac
      isMac = /Mac/.test(navigator.userAgent)
      if (isMac) {
        return e.metaKey
      } else {
        return e.ctrlKey
      }
    }

    Util.prototype.isEmptyNode = function(node) {
      var $node
      $node = $(node)
      return $node.is(':empty') || (!$node.text() && !$node.find(':not(br, span, div)').length)
    }

    Util.prototype.isDecoratedNode = function(node) {
      return $(node).is('[class^="simditor-"]')
    }

    Util.prototype.blockNodes = ['div', 'p', 'ul', 'ol', 'li', 'blockquote', 'hr', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'table']

    Util.prototype.isBlockNode = function(node) {
      node = $(node)[0]
      if (!node || node.nodeType === 3) {
        return false
      }
      return new RegExp('^(' + (this.blockNodes.join('|')) + ')$').test(node.nodeName.toLowerCase())
    }

    Util.prototype.getNodeLength = function(node) {
      node = $(node)[0]
      switch (node.nodeType) {
        case 7:
        case 10:
          return 0
        case 3:
        case 8:
          return node.length
        default:
          return node.childNodes.length
      }
    }

    Util.prototype.dataURLtoBlob = function(dataURL) {
      var BlobBuilder, arrayBuffer, bb, blobArray, byteString, hasArrayBufferViewSupport, hasBlobConstructor, i, intArray, k, mimeString, ref, supportBlob
      hasBlobConstructor = window.Blob && (function() {
        var e
        try {
          return Boolean(new Blob())
        } catch (_error) {
          e = _error
          return false
        }
      })()
      hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && (function() {
        var e
        try {
          return new Blob([new Uint8Array(100)]).size === 100
        } catch (_error) {
          e = _error
          return false
        }
      })()
      BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder
      supportBlob = hasBlobConstructor || BlobBuilder
      if (!(supportBlob && window.atob && window.ArrayBuffer && window.Uint8Array)) {
        return false
      }
      if (dataURL.split(',')[0].indexOf('base64') >= 0) {
        byteString = atob(dataURL.split(',')[1])
      } else {
        byteString = decodeURIComponent(dataURL.split(',')[1])
      }
      arrayBuffer = new ArrayBuffer(byteString.length)
      intArray = new Uint8Array(arrayBuffer)
      for (i = k = 0, ref = byteString.length; ref >= 0 ? k <= ref : k >= ref; i = ref >= 0 ? ++k : --k) {
        intArray[i] = byteString.charCodeAt(i)
      }
      mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
      if (hasBlobConstructor) {
        blobArray = hasArrayBufferViewSupport ? intArray : arrayBuffer
        return new Blob([blobArray], {
          type: mimeString
        })
      }
      bb = new BlobBuilder()
      bb.append(arrayBuffer)
      return bb.getBlob(mimeString)
    }

    Util.prototype.throttle = function(func, wait) {
      var args, call, ctx, last, rtn, throttled, timeoutID
      last = 0
      timeoutID = 0
      ctx = args = rtn = null
      call = function() {
        timeoutID = 0
        last = +new Date()
        rtn = func.apply(ctx, args)
        ctx = null
        return args = null
      }
      throttled = function() {
        var delta
        ctx = this
        args = arguments
        delta = new Date() - last
        if (!timeoutID) {
          if (delta >= wait) {
            call()
          } else {
            timeoutID = setTimeout(call, wait - delta)
          }
        }
        return rtn
      }
      throttled.clear = function() {
        if (!timeoutID) {
          return
        }
        clearTimeout(timeoutID)
        return call()
      }
      return throttled
    }

    Util.prototype.formatHTML = function(html) {
      var cursor, indentString, lastMatch, level, match, re, repeatString, result, str
      re = /<(\/?)(.+?)(\/?)>/g
      result = ''
      level = 0
      lastMatch = null
      indentString = '  '
      repeatString = function(str, n) {
        return new Array(n + 1).join(str)
      }
      while ((match = re.exec(html)) !== null) {
        match.isBlockNode = $.inArray(match[2], this.blockNodes) > -1
        match.isStartTag = match[1] !== '/' && match[3] !== '/'
        match.isEndTag = match[1] === '/' || match[3] === '/'
        cursor = lastMatch ? lastMatch.index + lastMatch[0].length : 0
        if ((str = html.substring(cursor, match.index)).length > 0 && $.trim(str)) {
          result += str
        }
        if (match.isBlockNode && match.isEndTag && !match.isStartTag) {
          level -= 1
        }
        if (match.isBlockNode && match.isStartTag) {
          if (!(lastMatch && lastMatch.isBlockNode && lastMatch.isEndTag)) {
            result += '\n'
          }
          result += repeatString(indentString, level)
        }
        result += match[0]
        if (match.isBlockNode && match.isEndTag) {
          result += '\n'
        }
        if (match.isBlockNode && match.isStartTag) {
          level += 1
        }
        lastMatch = match
      }
      return $.trim(result)
    }

    return Util
  })(SimpleModule)

  Toolbar = (function(superClass) {
    extend(Toolbar, superClass)

    function Toolbar() {
      return Toolbar.__super__.constructor.apply(this, arguments)
    }

    Toolbar.pluginName = 'Toolbar'

    Toolbar.prototype.opts = {
      toolbar: true,
      toolbarFloat: true,
      toolbarHidden: false,
      toolbarFloatOffset: 0
    }

    Toolbar.prototype._tpl = {
      wrapper: '<div class="simditor-toolbar"><ul></ul></div>',
      separator: '<li><span class="separator"></span></li>'
    }

    Toolbar.prototype._init = function() {
      var floatInitialized, initToolbarFloat, toolbarHeight
      this.editor = this._module
      if (!this.opts.toolbar) {
        return
      }
      if (!$.isArray(this.opts.toolbar)) {
        this.opts.toolbar = ['bold', 'italic', 'underline', 'strikethrough', '|', 'ol', 'ul', 'blockquote', 'code', '|', 'link', 'image', '|', 'indent', 'outdent']
      }
      this._render()
      this.list.on('click', function(e) {
        return false
      })
      this.wrapper.on('mousedown', (function(_this) {
        return function(e) {
          return _this.list.find('.menu-on').removeClass('.menu-on')
        }
      })(this))
      $(document).on('mousedown.simditor' + this.editor.id, (function(_this) {
        return function(e) {
          return _this.list.find('.menu-on').removeClass('.menu-on')
        }
      })(this))
      if (!this.opts.toolbarHidden && this.opts.toolbarFloat) {
        this.wrapper.css('top', this.opts.toolbarFloatOffset)
        toolbarHeight = 0
        initToolbarFloat = (function(_this) {
          return function() {
            _this.wrapper.css('position', 'static')
            _this.wrapper.width('auto')
            _this.editor.util.reflow(_this.wrapper)
            _this.wrapper.width(_this.wrapper.outerWidth())
            _this.wrapper.css('left', _this.editor.util.os.mobile ? _this.wrapper.position().left : _this.wrapper.offset().left)
            _this.wrapper.css('position', '')
            toolbarHeight = _this.wrapper.outerHeight()
            _this.editor.placeholderEl.css('top', toolbarHeight)
            return true
          }
        })(this)
        floatInitialized = null
        $(window).on('resize.simditor-' + this.editor.id, function(e) {
          return floatInitialized = initToolbarFloat()
        })
        $(window).on('scroll.simditor-' + this.editor.id, (function(_this) {
          return function(e) {
            var bottomEdge, scrollTop, topEdge
            if (!_this.wrapper.is(':visible')) {
              return
            }
            topEdge = _this.editor.wrapper.offset().top
            bottomEdge = topEdge + _this.editor.wrapper.outerHeight() - 80
            scrollTop = $(document).scrollTop() + _this.opts.toolbarFloatOffset
            if (scrollTop <= topEdge || scrollTop >= bottomEdge) {
              _this.editor.wrapper.removeClass('toolbar-floating').css('padding-top', '')
              if (_this.editor.util.os.mobile) {
                return _this.wrapper.css('top', _this.opts.toolbarFloatOffset)
              }
            } else {
              floatInitialized || (floatInitialized = initToolbarFloat())
              _this.editor.wrapper.addClass('toolbar-floating').css('padding-top', toolbarHeight)
              if (_this.editor.util.os.mobile) {
                return _this.wrapper.css('top', scrollTop - topEdge + _this.opts.toolbarFloatOffset)
              }
            }
          }
        })(this))
      }
      this.editor.on('destroy', (function(_this) {
        return function() {
          return _this.buttons.length = 0
        }
      })(this))
      return $(document).on('mousedown.simditor-' + this.editor.id, (function(_this) {
        return function(e) {
          return _this.list.find('li.menu-on').removeClass('menu-on')
        }
      })(this))
    }

    Toolbar.prototype._render = function() {
      var k, len, name, ref
      this.buttons = []
      this.wrapper = $(this._tpl.wrapper).prependTo(this.editor.wrapper)
      this.list = this.wrapper.find('ul')
      ref = this.opts.toolbar
      for (k = 0, len = ref.length; k < len; k++) {
        name = ref[k]
        if (name === '|') {
          $(this._tpl.separator).appendTo(this.list)
          continue
        }
        if (!this.constructor.buttons[name]) {
          throw new Error('simditor: invalid toolbar button ' + name)
          continue
        }
        this.buttons.push(new this.constructor.buttons[name]({
          editor: this.editor
        }))
      }
      if (this.opts.toolbarHidden) {
        return this.wrapper.hide()
      }
    }

    Toolbar.prototype.findButton = function(name) {
      var button
      button = this.list.find('.toolbar-item-' + name).data('button')
      return button != null ? button : null
    }

    Toolbar.addButton = function(btn) {
      return this.buttons[btn.prototype.name] = btn
    }

    Toolbar.buttons = {}

    return Toolbar
  })(SimpleModule)

  Indentation = (function(superClass) {
    extend(Indentation, superClass)

    function Indentation() {
      return Indentation.__super__.constructor.apply(this, arguments)
    }

    Indentation.pluginName = 'Indentation'

    Indentation.prototype.opts = {
      tabIndent: true
    }

    Indentation.prototype._init = function() {
      this.editor = this._module
      return this.editor.keystroke.add('tab', '*', (function(_this) {
        return function(e) {
          var codeButton
          codeButton = _this.editor.toolbar.findButton('code')
          if (!(_this.opts.tabIndent || (codeButton && codeButton.active))) {
            return
          }
          return _this.indent(e.shiftKey)
        }
      })(this))
    }

    Indentation.prototype.indent = function(isBackward) {
      var $blockNodes, $endNodes, $startNodes, nodes, result
      $startNodes = this.editor.selection.startNodes()
      $endNodes = this.editor.selection.endNodes()
      $blockNodes = this.editor.selection.blockNodes()
      nodes = []
      $blockNodes = $blockNodes.each(function(i, node) {
        var include, j, k, len, n
        include = true
        for (j = k = 0, len = nodes.length; k < len; j = ++k) {
          n = nodes[j]
          if ($.contains(node, n)) {
            include = false
            break
          } else if ($.contains(n, node)) {
            nodes.splice(j, 1, node)
            include = false
            break
          }
        }
        if (include) {
          return nodes.push(node)
        }
      })
      $blockNodes = $(nodes)
      result = false
      $blockNodes.each((function(_this) {
        return function(i, blockEl) {
          var r
          r = isBackward ? _this.outdentBlock(blockEl) : _this.indentBlock(blockEl)
          if (r) {
            return result = r
          }
        }
      })(this))
      return result
    }

    Indentation.prototype.indentBlock = function(blockEl) {
      var $blockEl, $childList, $nextTd, $nextTr, $parentLi, $pre, $td, $tr, marginLeft, tagName
      $blockEl = $(blockEl)
      if (!$blockEl.length) {
        return
      }
      if ($blockEl.is('pre')) {
        $pre = this.editor.selection.containerNode()
        if (!($pre.is($blockEl) || $pre.closest('pre').is($blockEl))) {
          return
        }
        this.indentText(this.editor.selection.range())
      } else if ($blockEl.is('li')) {
        $parentLi = $blockEl.prev('li')
        if ($parentLi.length < 1) {
          return
        }
        this.editor.selection.save()
        tagName = $blockEl.parent()[0].tagName
        $childList = $parentLi.children('ul, ol')
        if ($childList.length > 0) {
          $childList.append($blockEl)
        } else {
          $('<' + tagName + '/>').append($blockEl).appendTo($parentLi)
        }
        this.editor.selection.restore()
      } else if ($blockEl.is('p, h1, h2, h3, h4')) {
        marginLeft = parseInt($blockEl.css('margin-left')) || 0
        marginLeft = (Math.round(marginLeft / this.opts.indentWidth) + 1) * this.opts.indentWidth
        $blockEl.css('margin-left', marginLeft)
      } else if ($blockEl.is('table') || $blockEl.is('.simditor-table')) {
        $td = this.editor.selection.containerNode().closest('td, th')
        $nextTd = $td.next('td, th')
        if (!($nextTd.length > 0)) {
          $tr = $td.parent('tr')
          $nextTr = $tr.next('tr')
          if ($nextTr.length < 1 && $tr.parent().is('thead')) {
            $nextTr = $tr.parent('thead').next('tbody').find('tr:first')
          }
          $nextTd = $nextTr.find('td:first, th:first')
        }
        if (!($td.length > 0 && $nextTd.length > 0)) {
          return
        }
        this.editor.selection.setRangeAtEndOf($nextTd)
      } else {
        return false
      }
      return true
    }

    Indentation.prototype.indentText = function(range) {
      var text, textNode
      text = range.toString().replace(/^(?=.+)/mg, '\u00A0\u00A0')
      textNode = document.createTextNode(text || '\u00A0\u00A0')
      range.deleteContents()
      range.insertNode(textNode)
      if (text) {
        range.selectNode(textNode)
        return this.editor.selection.range(range)
      } else {
        return this.editor.selection.setRangeAfter(textNode)
      }
    }

    Indentation.prototype.outdentBlock = function(blockEl) {
      var $blockEl, $parent, $parentLi, $pre, $prevTd, $prevTr, $td, $tr, marginLeft, range
      $blockEl = $(blockEl)
      if (!($blockEl && $blockEl.length > 0)) {
        return
      }
      if ($blockEl.is('pre')) {
        $pre = this.editor.selection.containerNode()
        if (!($pre.is($blockEl) || $pre.closest('pre').is($blockEl))) {
          return
        }
        this.outdentText(range)
      } else if ($blockEl.is('li')) {
        $parent = $blockEl.parent()
        $parentLi = $parent.parent('li')
        this.editor.selection.save()
        if ($parentLi.length < 1) {
          range = document.createRange()
          range.setStartBefore($parent[0])
          range.setEndBefore($blockEl[0])
          $parent.before(range.extractContents())
          $('<p/>').insertBefore($parent).after($blockEl.children('ul, ol')).append($blockEl.contents())
          $blockEl.remove()
        } else {
          if ($blockEl.next('li').length > 0) {
            $('<' + $parent[0].tagName + '/>').append($blockEl.nextAll('li')).appendTo($blockEl)
          }
          $blockEl.insertAfter($parentLi)
          if ($parent.children('li').length < 1) {
            $parent.remove()
          }
        }
        this.editor.selection.restore()
      } else if ($blockEl.is('p, h1, h2, h3, h4')) {
        marginLeft = parseInt($blockEl.css('margin-left')) || 0
        marginLeft = Math.max(Math.round(marginLeft / this.opts.indentWidth) - 1, 0) * this.opts.indentWidth
        $blockEl.css('margin-left', marginLeft === 0 ? '' : marginLeft)
      } else if ($blockEl.is('table') || $blockEl.is('.simditor-table')) {
        $td = this.editor.selection.containerNode().closest('td, th')
        $prevTd = $td.prev('td, th')
        if (!($prevTd.length > 0)) {
          $tr = $td.parent('tr')
          $prevTr = $tr.prev('tr')
          if ($prevTr.length < 1 && $tr.parent().is('tbody')) {
            $prevTr = $tr.parent('tbody').prev('thead').find('tr:first')
          }
          $prevTd = $prevTr.find('td:last, th:last')
        }
        if (!($td.length > 0 && $prevTd.length > 0)) {
          return
        }
        this.editor.selection.setRangeAtEndOf($prevTd)
      } else {
        return false
      }
      return true
    }

    Indentation.prototype.outdentText = function(range) {}

    return Indentation
  })(SimpleModule)

  Clipboard = (function(superClass) {
    extend(Clipboard, superClass)

    function Clipboard() {
      return Clipboard.__super__.constructor.apply(this, arguments)
    }

    Clipboard.pluginName = 'Clipboard'

    Clipboard.prototype.opts = {
      pasteImage: false,
      cleanPaste: false
    }

    Clipboard.prototype._init = function() {
      this.editor = this._module
      if (this.opts.pasteImage && typeof this.opts.pasteImage !== 'string') {
        this.opts.pasteImage = 'inline'
      }
      return this.editor.body.on('paste', (function(_this) {
        return function(e) {
          var range
          if (_this.pasting || _this._pasteBin) {
            return
          }
          if (_this.editor.triggerHandler(e) === false) {
            return false
          }
          range = _this.editor.selection.deleteRangeContents()
          if (_this.editor.body.html()) {
            if (!range.collapsed) {
              range.collapse(true)
            }
          } else {
            _this.editor.formatter.format()
            _this.editor.selection.setRangeAtStartOf(_this.editor.body.find('p:first'))
          }
          if (_this._processPasteByClipboardApi(e)) {
            return false
          }
          _this.editor.inputManager.throttledValueChanged.clear()
          _this.editor.inputManager.throttledSelectionChanged.clear()
          _this.editor.undoManager.throttledPushState.clear()
          _this.editor.selection.reset()
          _this.editor.undoManager.resetCaretPosition()
          _this.pasting = true
          return _this._getPasteContent(function(pasteContent) {
            _this._processPasteContent(pasteContent)
            _this._pasteInBlockEl = null
            _this._pastePlainText = null
            return _this.pasting = false
          })
        }
      })(this))
    }

    Clipboard.prototype._processPasteByClipboardApi = function(e) {
      var imageFile, pasteItem, ref, uploadOpt
      if (this.editor.util.browser.edge) {
        return
      }
      if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.items && e.originalEvent.clipboardData.items.length > 0) {
        pasteItem = e.originalEvent.clipboardData.items[0]
        if (/^image\//.test(pasteItem.type)) {
          imageFile = pasteItem.getAsFile()
          if (!((imageFile != null) && this.opts.pasteImage)) {
            return
          }
          if (!imageFile.name) {
            imageFile.name = 'Clipboard Image.png'
          }
          if (this.editor.triggerHandler('pasting', [imageFile]) === false) {
            return
          }
          uploadOpt = {}
          uploadOpt[this.opts.pasteImage] = true
          if ((ref = this.editor.uploader) != null) {
            ref.upload(imageFile, uploadOpt)
          }
          return true
        }
      }
    }

    Clipboard.prototype._getPasteContent = function(callback) {
      var state
      this._pasteBin = $('<div contenteditable="true" />').addClass('simditor-paste-bin').attr('tabIndex', '-1').appendTo(this.editor.el)
      state = {
        html: this.editor.body.html(),
        caret: this.editor.undoManager.caretPosition()
      }
      this._pasteBin.focus()
      return setTimeout((function(_this) {
        return function() {
          var pasteContent
          _this.editor.hidePopover()
          _this.editor.body.get(0).innerHTML = state.html
          _this.editor.undoManager.caretPosition(state.caret)
          _this.editor.body.focus()
          _this.editor.selection.reset()
          _this.editor.selection.range()
          _this._pasteInBlockEl = _this.editor.selection.blockNodes().last()
          _this._pastePlainText = _this.opts.cleanPaste || _this._pasteInBlockEl.is('pre, table')
          if (_this._pastePlainText) {
            pasteContent = _this.editor.formatter.clearHtml(_this._pasteBin.html(), true)
          } else {
            pasteContent = $('<div/>').append(_this._pasteBin.contents())
            pasteContent.find('table colgroup').remove()
            _this.editor.formatter.format(pasteContent)
            _this.editor.formatter.decorate(pasteContent)
            _this.editor.formatter.beautify(pasteContent.children())
            pasteContent = pasteContent.contents()
          }
          _this._pasteBin.remove()
          _this._pasteBin = null
          return callback(pasteContent)
        }
      })(this), 0)
    }

    Clipboard.prototype._processPasteContent = function(pasteContent) {
      var $blockEl, $img, blob, children, insertPosition, k, l, lastLine, len, len1, len2, len3, len4, line, lines, m, node, o, q, ref, ref1, ref2, uploadOpt
      if (this.editor.triggerHandler('pasting', [pasteContent]) === false) {
        return
      }
      $blockEl = this._pasteInBlockEl
      if (!pasteContent) {
        return
      } else if (this._pastePlainText) {
        if ($blockEl.is('table')) {
          lines = pasteContent.split('\n')
          lastLine = lines.pop()
          for (k = 0, len = lines.length; k < len; k++) {
            line = lines[k]
            this.editor.selection.insertNode(document.createTextNode(line))
            this.editor.selection.insertNode($('<br/>'))
          }
          this.editor.selection.insertNode(document.createTextNode(lastLine))
        } else {
          pasteContent = $('<div/>').text(pasteContent)
          ref = pasteContent.contents()
          for (l = 0, len1 = ref.length; l < len1; l++) {
            node = ref[l]
            this.editor.selection.insertNode($(node)[0])
          }
        }
      } else if ($blockEl.is(this.editor.body)) {
        for (m = 0, len2 = pasteContent.length; m < len2; m++) {
          node = pasteContent[m]
          this.editor.selection.insertNode(node)
        }
      } else if (pasteContent.length < 1) {
        return
      } else if (pasteContent.length === 1) {
        if (pasteContent.is('p')) {
          children = pasteContent.contents()
          if (children.length === 1 && children.is('img')) {
            $img = children
            if (/^data:image/.test($img.attr('src'))) {
              if (!this.opts.pasteImage) {
                return
              }
              blob = this.editor.util.dataURLtoBlob($img.attr('src'))
              blob.name = 'Clipboard Image.png'
              uploadOpt = {}
              uploadOpt[this.opts.pasteImage] = true
              if ((ref1 = this.editor.uploader) != null) {
                ref1.upload(blob, uploadOpt)
              }
              return
            } else if ($img.is('img[src^="webkit-fake-url://"]')) {
              return
            }
          }
          for (o = 0, len3 = children.length; o < len3; o++) {
            node = children[o]
            this.editor.selection.insertNode(node)
          }
        } else if ($blockEl.is('p') && this.editor.util.isEmptyNode($blockEl)) {
          $blockEl.replaceWith(pasteContent)
          this.editor.selection.setRangeAtEndOf(pasteContent)
        } else if (pasteContent.is('ul, ol')) {
          if (pasteContent.find('li').length === 1) {
            pasteContent = $('<div/>').text(pasteContent.text())
            ref2 = pasteContent.contents()
            for (q = 0, len4 = ref2.length; q < len4; q++) {
              node = ref2[q]
              this.editor.selection.insertNode($(node)[0])
            }
          } else if ($blockEl.is('li')) {
            $blockEl.parent().after(pasteContent)
            this.editor.selection.setRangeAtEndOf(pasteContent)
          } else {
            $blockEl.after(pasteContent)
            this.editor.selection.setRangeAtEndOf(pasteContent)
          }
        } else {
          $blockEl.after(pasteContent)
          this.editor.selection.setRangeAtEndOf(pasteContent)
        }
      } else {
        if ($blockEl.is('li')) {
          $blockEl = $blockEl.parent()
        }
        if (this.editor.selection.rangeAtStartOf($blockEl)) {
          insertPosition = 'before'
        } else if (this.editor.selection.rangeAtEndOf($blockEl)) {
          insertPosition = 'after'
        } else {
          this.editor.selection.breakBlockEl($blockEl)
          insertPosition = 'before'
        }
        $blockEl[insertPosition](pasteContent)
        this.editor.selection.setRangeAtEndOf(pasteContent.last())
      }
      return this.editor.inputManager.throttledValueChanged()
    }

    return Clipboard
  })(SimpleModule)

  Simditor = (function(superClass) {
    extend(Simditor, superClass)

    function Simditor() {
      return Simditor.__super__.constructor.apply(this, arguments)
    }

    Simditor.connect(Util)

    Simditor.connect(InputManager)

    Simditor.connect(Selection)

    Simditor.connect(UndoManager)

    Simditor.connect(Keystroke)

    Simditor.connect(Formatter)

    Simditor.connect(Toolbar)

    Simditor.connect(Indentation)

    Simditor.connect(Clipboard)

    Simditor.count = 0

    Simditor.prototype.opts = {
      textarea: null,
      placeholder: '',
      defaultImage: 'images/image.png',
      params: {},
      upload: false,
      indentWidth: 40
    }

    Simditor.prototype._init = function() {
      var e, editor, form, uploadOpts
      this.textarea = $(this.opts.textarea)
      this.opts.placeholder = this.opts.placeholder || this.textarea.attr('placeholder')
      if (!this.textarea.length) {
        throw new Error('simditor: param textarea is required.')
        return
      }
      editor = this.textarea.data('simditor')
      if (editor != null) {
        editor.destroy()
      }
      this.id = ++Simditor.count
      this._render()
      if (simpleHotkeys) {
        this.hotkeys = simpleHotkeys({
          el: this.body
        })
      } else {
        throw new Error('simditor: simple-hotkeys is required.')
        return
      }
      if (this.opts.upload && simpleUploader) {
        uploadOpts = typeof this.opts.upload === 'object' ? this.opts.upload : {}
        this.uploader = simpleUploader(uploadOpts)
      }
      form = this.textarea.closest('form')
      if (form.length) {
        form.on('submit.simditor-' + this.id, (function(_this) {
          return function() {
            return _this.sync()
          }
        })(this))
        form.on('reset.simditor-' + this.id, (function(_this) {
          return function() {
            return _this.setValue('')
          }
        })(this))
      }
      this.on('initialized', (function(_this) {
        return function() {
          if (_this.opts.placeholder) {
            _this.on('valuechanged', function() {
              return _this._placeholder()
            })
          }
          _this.setValue(_this.textarea.val().trim() || '')
          if (_this.textarea.attr('autofocus')) {
            return _this.focus()
          }
        }
      })(this))
      if (this.util.browser.mozilla) {
        this.util.reflow()
        try {
          document.execCommand('enableObjectResizing', false, false)
          return document.execCommand('enableInlineTableEditing', false, false)
        } catch (_error) {
          e = _error
        }
      }
    }

    Simditor.prototype._tpl = '<div class="simditor">\n  <div class="simditor-wrapper">\n    <div class="simditor-placeholder"></div>\n    <div class="simditor-body" contenteditable="true">\n    </div>\n  </div>\n</div>'

    Simditor.prototype._render = function() {
      var key, ref, results, val
      this.el = $(this._tpl).insertBefore(this.textarea)
      this.wrapper = this.el.find('.simditor-wrapper')
      this.body = this.wrapper.find('.simditor-body')
      this.placeholderEl = this.wrapper.find('.simditor-placeholder').append(this.opts.placeholder)
      this.el.data('simditor', this)
      this.wrapper.append(this.textarea)
      this.textarea.data('simditor', this).blur()
      this.body.attr('tabindex', this.textarea.attr('tabindex'))
      if (this.util.os.mac) {
        this.el.addClass('simditor-mac')
      } else if (this.util.os.linux) {
        this.el.addClass('simditor-linux')
      }
      if (this.util.os.mobile) {
        this.el.addClass('simditor-mobile')
      }
      if (this.opts.params) {
        ref = this.opts.params
        results = []
        for (key in ref) {
          val = ref[key]
          results.push($('<input/>', {
            type: 'hidden',
            name: key,
            value: val
          }).insertAfter(this.textarea))
        }
        return results
      }
    }

    Simditor.prototype._placeholder = function() {
      var children
      children = this.body.children()
      if (children.length === 0 || (children.length === 1 && this.util.isEmptyNode(children) && parseInt(children.css('margin-left') || 0) < this.opts.indentWidth)) {
        return this.placeholderEl.show()
      } else {
        return this.placeholderEl.hide()
      }
    }

    Simditor.prototype.setValue = function(val) {
      this.hidePopover()
      this.textarea.val(val)
      this.body.get(0).innerHTML = val
      this.formatter.format()
      this.formatter.decorate()
      this.util.reflow(this.body)
      this.inputManager.lastCaretPosition = null
      return this.trigger('valuechanged')
    }

    Simditor.prototype.getValue = function() {
      return this.sync()
    }

    Simditor.prototype.sync = function() {
      var children, cloneBody, emptyP, firstP, lastP, val
      cloneBody = this.body.clone()
      this.formatter.undecorate(cloneBody)
      this.formatter.format(cloneBody)
      this.formatter.autolink(cloneBody)
      children = cloneBody.children()
      lastP = children.last('p')
      firstP = children.first('p')
      while (lastP.is('p') && this.util.isEmptyNode(lastP)) {
        emptyP = lastP
        lastP = lastP.prev('p')
        emptyP.remove()
      }
      while (firstP.is('p') && this.util.isEmptyNode(firstP)) {
        emptyP = firstP
        firstP = lastP.next('p')
        emptyP.remove()
      }
      cloneBody.find('img.uploading').remove()
      val = $.trim(cloneBody.html())
      this.textarea.val(val)
      return val
    }

    Simditor.prototype.focus = function() {
      var $blockEl, range
      if (!(this.body.is(':visible') && this.body.is('[contenteditable]'))) {
        this.el.find('textarea:visible').focus()
        return
      }
      if (this.inputManager.lastCaretPosition) {
        this.undoManager.caretPosition(this.inputManager.lastCaretPosition)
        return this.inputManager.lastCaretPosition = null
      } else {
        $blockEl = this.body.children().last()
        if (!$blockEl.is('p')) {
          $blockEl = $('<p/>').append(this.util.phBr).appendTo(this.body)
        }
        range = document.createRange()
        return this.selection.setRangeAtEndOf($blockEl, range)
      }
    }

    Simditor.prototype.blur = function() {
      if (this.body.is(':visible') && this.body.is('[contenteditable]')) {
        return this.body.blur()
      } else {
        return this.body.find('textarea:visible').blur()
      }
    }

    Simditor.prototype.hidePopover = function() {
      return this.el.find('.simditor-popover').each(function(i, popover) {
        popover = $(popover).data('popover')
        if (popover.active) {
          return popover.hide()
        }
      })
    }

    Simditor.prototype.destroy = function() {
      this.triggerHandler('destroy')
      this.textarea.closest('form').off('.simditor .simditor-' + this.id)
      this.selection.clear()
      this.inputManager.focused = false
      this.textarea.insertBefore(this.el).hide().val('').removeData('simditor')
      this.el.remove()
      $(document).off('.simditor-' + this.id)
      $(window).off('.simditor-' + this.id)
      return this.off()
    }

    return Simditor
  })(SimpleModule)

  Simditor.i18n = {
    'zh-CN': {
      'blockquote': '引用',
      'bold': '加粗文字',
      'code': '插入代码',
      'color': '文字颜色',
      'coloredText': '彩色文字',
      'hr': '分隔线',
      'image': '插入图片',
      'externalImage': '外链图片',
      'uploadImage': '上传图片',
      'uploadFailed': '上传失败了',
      'uploadError': '上传出错了',
      'imageUrl': '图片地址',
      'imageSize': '图片尺寸',
      'imageAlt': '图片描述',
      'restoreImageSize': '还原图片尺寸',
      'uploading': '正在上传',
      'indent': '向右缩进',
      'outdent': '向左缩进',
      'italic': '斜体文字',
      'link': '插入链接',
      'linkText': '链接文字',
      'linkUrl': '链接地址',
      'linkTarget': '打开方式',
      'openLinkInCurrentWindow': '在新窗口中打开',
      'openLinkInNewWindow': '在当前窗口中打开',
      'removeLink': '移除链接',
      'ol': '有序列表',
      'ul': '无序列表',
      'strikethrough': '删除线文字',
      'table': '表格',
      'deleteRow': '删除行',
      'insertRowAbove': '在上面插入行',
      'insertRowBelow': '在下面插入行',
      'deleteColumn': '删除列',
      'insertColumnLeft': '在左边插入列',
      'insertColumnRight': '在右边插入列',
      'deleteTable': '删除表格',
      'title': '标题',
      'normalText': '普通文本',
      'underline': '下划线文字',
      'alignment': '水平对齐',
      'alignCenter': '居中',
      'alignLeft': '居左',
      'alignRight': '居右',
      'selectLanguage': '选择程序语言',
      'fontScale': '字体大小',
      'fontScaleXLarge': '超大字体',
      'fontScaleLarge': '大号字体',
      'fontScaleNormal': '正常大小',
      'fontScaleSmall': '小号字体',
      'fontScaleXSmall': '超小字体'
    },
    'en-US': {
      'blockquote': 'Block Quote',
      'bold': 'Bold',
      'code': 'Code',
      'color': 'Text Color',
      'coloredText': 'Colored Text',
      'hr': 'Horizontal Line',
      'image': 'Insert Image',
      'externalImage': 'External Image',
      'uploadImage': 'Upload Image',
      'uploadFailed': 'Upload failed',
      'uploadError': 'Error occurs during upload',
      'imageUrl': 'Url',
      'imageSize': 'Size',
      'imageAlt': 'Alt',
      'restoreImageSize': 'Restore Origin Size',
      'uploading': 'Uploading',
      'indent': 'Indent',
      'outdent': 'Outdent',
      'italic': 'Italic',
      'link': 'Insert Link',
      'linkText': 'Text',
      'linkUrl': 'Url',
      'linkTarget': 'Target',
      'openLinkInCurrentWindow': 'Open link in current window',
      'openLinkInNewWindow': 'Open link in new window',
      'removeLink': 'Remove Link',
      'ol': 'Ordered List',
      'ul': 'Unordered List',
      'strikethrough': 'Strikethrough',
      'table': 'Table',
      'deleteRow': 'Delete Row',
      'insertRowAbove': 'Insert Row Above',
      'insertRowBelow': 'Insert Row Below',
      'deleteColumn': 'Delete Column',
      'insertColumnLeft': 'Insert Column Left',
      'insertColumnRight': 'Insert Column Right',
      'deleteTable': 'Delete Table',
      'title': 'Title',
      'normalText': 'Text',
      'underline': 'Underline',
      'alignment': 'Alignment',
      'alignCenter': 'Align Center',
      'alignLeft': 'Align Left',
      'alignRight': 'Align Right',
      'selectLanguage': 'Select Language',
      'fontScale': 'Font Size',
      'fontScaleXLarge': 'X Large Size',
      'fontScaleLarge': 'Large Size',
      'fontScaleNormal': 'Normal Size',
      'fontScaleSmall': 'Small Size',
      'fontScaleXSmall': 'X Small Size'
    }
  }

  Button = (function(superClass) {
    extend(Button, superClass)

    Button.prototype._tpl = {
      item: '<li><a tabindex="-1" unselectable="on" class="toolbar-item" href="javascript:;"><span></span></a></li>',
      menuWrapper: '<div class="toolbar-menu"></div>',
      menuItem: '<li><a tabindex="-1" unselectable="on" class="menu-item" href="javascript:;"><span></span></a></li>',
      separator: '<li><span class="separator"></span></li>'
    }

    Button.prototype.name = ''

    Button.prototype.icon = ''

    Button.prototype.title = ''

    Button.prototype.text = ''

    Button.prototype.htmlTag = ''

    Button.prototype.disableTag = ''

    Button.prototype.menu = false

    Button.prototype.active = false

    Button.prototype.disabled = false

    Button.prototype.needFocus = true

    Button.prototype.shortcut = null

    function Button(opts) {
      this.editor = opts.editor
      this.title = this._t(this.name)
      Button.__super__.constructor.call(this, opts)
    }

    Button.prototype._init = function() {
      var k, len, ref, tag
      this.render()
      this.el.on('mousedown', (function(_this) {
        return function(e) {
          var exceed, noFocus, param
          e.preventDefault()
          noFocus = _this.needFocus && !_this.editor.inputManager.focused
          if (_this.el.hasClass('disabled') || noFocus) {
            return false
          }
          if (_this.menu) {
            _this.wrapper.toggleClass('menu-on').siblings('li').removeClass('menu-on')
            if (_this.wrapper.is('.menu-on')) {
              exceed = _this.menuWrapper.offset().left + _this.menuWrapper.outerWidth() + 5 - _this.editor.wrapper.offset().left - _this.editor.wrapper.outerWidth()
              if (exceed > 0) {
                _this.menuWrapper.css({
                  'left': 'auto',
                  'right': 0
                })
              }
              _this.trigger('menuexpand')
            }
            return false
          }
          param = _this.el.data('param')
          _this.command(param)
          return false
        }
      })(this))
      this.wrapper.on('click', 'a.menu-item', (function(_this) {
        return function(e) {
          var btn, noFocus, param
          e.preventDefault()
          btn = $(e.currentTarget)
          _this.wrapper.removeClass('menu-on')
          noFocus = _this.needFocus && !_this.editor.inputManager.focused
          if (btn.hasClass('disabled') || noFocus) {
            return false
          }
          _this.editor.toolbar.wrapper.removeClass('menu-on')
          param = btn.data('param')
          _this.command(param)
          return false
        }
      })(this))
      this.wrapper.on('mousedown', 'a.menu-item', function(e) {
        return false
      })
      this.editor.on('blur', (function(_this) {
        return function() {
          var editorActive
          editorActive = _this.editor.body.is(':visible') && _this.editor.body.is('[contenteditable]')
          if (!(editorActive && !_this.editor.clipboard.pasting)) {
            return
          }
          _this.setActive(false)
          return _this.setDisabled(false)
        }
      })(this))
      if (this.shortcut != null) {
        this.editor.hotkeys.add(this.shortcut, (function(_this) {
          return function(e) {
            _this.el.mousedown()
            return false
          }
        })(this))
      }
      ref = this.htmlTag.split(',')
      for (k = 0, len = ref.length; k < len; k++) {
        tag = ref[k]
        tag = $.trim(tag)
        if (tag && $.inArray(tag, this.editor.formatter._allowedTags) < 0) {
          this.editor.formatter._allowedTags.push(tag)
        }
      }
      return this.editor.on('selectionchanged', (function(_this) {
        return function(e) {
          if (_this.editor.inputManager.focused) {
            return _this._status()
          }
        }
      })(this))
    }

    Button.prototype.iconClassOf = function(icon) {
      if (icon) {
        return 'simditor-icon simditor-icon-' + icon
      } else {
        return ''
      }
    }

    Button.prototype.setIcon = function(icon) {
      return this.el.find('span').removeClass().addClass(this.iconClassOf(icon)).text(this.text)
    }

    Button.prototype.render = function() {
      this.wrapper = $(this._tpl.item).appendTo(this.editor.toolbar.list)
      this.el = this.wrapper.find('a.toolbar-item')
      this.el.attr('title', this.title).addClass('toolbar-item-' + this.name).data('button', this)
      this.setIcon(this.icon)
      if (!this.menu) {
        return
      }
      this.menuWrapper = $(this._tpl.menuWrapper).appendTo(this.wrapper)
      this.menuWrapper.addClass('toolbar-menu-' + this.name)
      return this.renderMenu()
    }

    Button.prototype.renderMenu = function() {
      var $menuBtnEl, $menuItemEl, k, len, menuItem, ref, ref1, results
      if (!$.isArray(this.menu)) {
        return
      }
      this.menuEl = $('<ul/>').appendTo(this.menuWrapper)
      ref = this.menu
      results = []
      for (k = 0, len = ref.length; k < len; k++) {
        menuItem = ref[k]
        if (menuItem === '|') {
          $(this._tpl.separator).appendTo(this.menuEl)
          continue
        }
        $menuItemEl = $(this._tpl.menuItem).appendTo(this.menuEl)
        $menuBtnEl = $menuItemEl.find('a.menu-item').attr({
          'title': (ref1 = menuItem.title) != null ? ref1 : menuItem.text,
          'data-param': menuItem.param
        }).addClass('menu-item-' + menuItem.name)
        if (menuItem.icon) {
          results.push($menuBtnEl.find('span').addClass(this.iconClassOf(menuItem.icon)))
        } else {
          results.push($menuBtnEl.find('span').text(menuItem.text))
        }
      }
      return results
    }

    Button.prototype.setActive = function(active) {
      if (active === this.active) {
        return
      }
      this.active = active
      return this.el.toggleClass('active', this.active)
    }

    Button.prototype.setDisabled = function(disabled) {
      if (disabled === this.disabled) {
        return
      }
      this.disabled = disabled
      return this.el.toggleClass('disabled', this.disabled)
    }

    Button.prototype._disableStatus = function() {
      var disabled, endNodes, startNodes
      startNodes = this.editor.selection.startNodes()
      endNodes = this.editor.selection.endNodes()
      disabled = startNodes.filter(this.disableTag).length > 0 || endNodes.filter(this.disableTag).length > 0
      this.setDisabled(disabled)
      if (this.disabled) {
        this.setActive(false)
      }
      return this.disabled
    }

    Button.prototype._activeStatus = function() {
      var active, endNode, endNodes, startNode, startNodes
      startNodes = this.editor.selection.startNodes()
      endNodes = this.editor.selection.endNodes()
      startNode = startNodes.filter(this.htmlTag)
      endNode = endNodes.filter(this.htmlTag)
      active = startNode.length > 0 && endNode.length > 0 && startNode.is(endNode)
      this.node = active ? startNode : null
      this.setActive(active)
      return this.active
    }

    Button.prototype._status = function() {
      this._disableStatus()
      if (this.disabled) {
        return
      }
      return this._activeStatus()
    }

    Button.prototype.command = function(param) {}

    Button.prototype._t = function() {
      var args, ref, result
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      result = Button.__super__._t.apply(this, args)
      if (!result) {
        result = (ref = this.editor)._t.apply(ref, args)
      }
      return result
    }

    return Button
  })(SimpleModule)

  Simditor.Button = Button

  Popover = (function(superClass) {
    extend(Popover, superClass)

    Popover.prototype.offset = {
      top: 4,
      left: 0
    }

    Popover.prototype.target = null

    Popover.prototype.active = false

    function Popover(opts) {
      this.button = opts.button
      this.editor = opts.button.editor
      Popover.__super__.constructor.call(this, opts)
    }

    Popover.prototype._init = function() {
      this.el = $('<div class="simditor-popover"></div>').appendTo(this.editor.el).data('popover', this)
      this.render()
      this.el.on('mouseenter', (function(_this) {
        return function(e) {
          return _this.el.addClass('hover')
        }
      })(this))
      return this.el.on('mouseleave', (function(_this) {
        return function(e) {
          return _this.el.removeClass('hover')
        }
      })(this))
    }

    Popover.prototype.render = function() {}

    Popover.prototype._initLabelWidth = function() {
      var $fields
      $fields = this.el.find('.settings-field')
      if (!($fields.length > 0)) {
        return
      }
      this._labelWidth = 0
      $fields.each((function(_this) {
        return function(i, field) {
          var $field, $label
          $field = $(field)
          $label = $field.find('label')
          if (!($label.length > 0)) {
            return
          }
          return _this._labelWidth = Math.max(_this._labelWidth, $label.width())
        }
      })(this))
      return $fields.find('label').width(this._labelWidth)
    }

    Popover.prototype.show = function($target, position) {
      if (position == null) {
        position = 'bottom'
      }
      if ($target == null) {
        return
      }
      this.el.siblings('.simditor-popover').each(function(i, popover) {
        popover = $(popover).data('popover')
        if (popover.active) {
          return popover.hide()
        }
      })
      if (this.active && this.target) {
        this.target.removeClass('selected')
      }
      this.target = $target.addClass('selected')
      if (this.active) {
        this.refresh(position)
        return this.trigger('popovershow')
      } else {
        this.active = true
        this.el.css({
          left: -9999
        }).show()
        if (!this._labelWidth) {
          this._initLabelWidth()
        }
        this.editor.util.reflow()
        this.refresh(position)
        return this.trigger('popovershow')
      }
    }

    Popover.prototype.hide = function() {
      if (!this.active) {
        return
      }
      if (this.target) {
        this.target.removeClass('selected')
      }
      this.target = null
      this.active = false
      this.el.hide()
      return this.trigger('popoverhide')
    }

    Popover.prototype.refresh = function(position) {
      var editorOffset, left, maxLeft, targetH, targetOffset, top
      if (position == null) {
        position = 'bottom'
      }
      if (!this.active) {
        return
      }
      editorOffset = this.editor.el.offset()
      targetOffset = this.target.offset()
      targetH = this.target.outerHeight()
      if (position === 'bottom') {
        top = targetOffset.top - editorOffset.top + targetH
      } else if (position === 'top') {
        top = targetOffset.top - editorOffset.top - this.el.height()
      }
      maxLeft = this.editor.wrapper.width() - this.el.outerWidth() - 10
      left = Math.min(targetOffset.left - editorOffset.left, maxLeft)
      return this.el.css({
        top: top + this.offset.top,
        left: left + this.offset.left
      })
    }

    Popover.prototype.destroy = function() {
      this.target = null
      this.active = false
      this.editor.off('.linkpopover')
      return this.el.remove()
    }

    Popover.prototype._t = function() {
      var args, ref, result
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      result = Popover.__super__._t.apply(this, args)
      if (!result) {
        result = (ref = this.button)._t.apply(ref, args)
      }
      return result
    }

    return Popover
  })(SimpleModule)

  Simditor.Popover = Popover

  TitleButton = (function(superClass) {
    extend(TitleButton, superClass)

    function TitleButton() {
      return TitleButton.__super__.constructor.apply(this, arguments)
    }

    TitleButton.prototype.name = 'title'

    TitleButton.prototype.htmlTag = 'h1, h2, h3, h4, h5'

    TitleButton.prototype.disableTag = 'pre, table'

    TitleButton.prototype._init = function() {
      this.menu = [
        {
          name: 'normal',
          text: this._t('normalText'),
          param: 'p'
        }, '|', {
          name: 'h1',
          text: this._t('title') + ' 1',
          param: 'h1'
        }, {
          name: 'h2',
          text: this._t('title') + ' 2',
          param: 'h2'
        }, {
          name: 'h3',
          text: this._t('title') + ' 3',
          param: 'h3'
        }, {
          name: 'h4',
          text: this._t('title') + ' 4',
          param: 'h4'
        }, {
          name: 'h5',
          text: this._t('title') + ' 5',
          param: 'h5'
        }
      ]
      return TitleButton.__super__._init.call(this)
    }

    TitleButton.prototype.setActive = function(active, param) {
      TitleButton.__super__.setActive.call(this, active)
      if (active) {
        param || (param = this.node[0].tagName.toLowerCase())
      }
      this.el.removeClass('active-p active-h1 active-h2 active-h3 active-h4 active-h5')
      if (active) {
        return this.el.addClass('active active-' + param)
      }
    }

    TitleButton.prototype.command = function(param) {
      var $rootNodes
      $rootNodes = this.editor.selection.rootNodes()
      this.editor.selection.save()
      $rootNodes.each((function(_this) {
        return function(i, node) {
          var $node
          $node = $(node)
          if ($node.is('blockquote') || $node.is(param) || $node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node)) {
            return
          }
          return $('<' + param + '/>').append($node.contents()).replaceAll($node)
        }
      })(this))
      this.editor.selection.restore()
      return this.editor.trigger('valuechanged')
    }

    return TitleButton
  })(Button)

  Simditor.Toolbar.addButton(TitleButton)

  FontScaleButton = (function(superClass) {
    extend(FontScaleButton, superClass)

    function FontScaleButton() {
      return FontScaleButton.__super__.constructor.apply(this, arguments)
    }

    FontScaleButton.prototype.name = 'fontScale'

    FontScaleButton.prototype.icon = 'font'

    FontScaleButton.prototype.disableTag = 'pre'

    FontScaleButton.prototype.htmlTag = 'span'

    FontScaleButton.prototype.sizeMap = {
      'x-large': '1.5em',
      'large': '1.25em',
      'small': '.75em',
      'x-small': '.5em'
    }

    FontScaleButton.prototype._init = function() {
      this.menu = [
        {
          name: '150%',
          text: this._t('fontScaleXLarge'),
          param: '5'
        }, {
          name: '125%',
          text: this._t('fontScaleLarge'),
          param: '4'
        }, {
          name: '100%',
          text: this._t('fontScaleNormal'),
          param: '3'
        }, {
          name: '75%',
          text: this._t('fontScaleSmall'),
          param: '2'
        }, {
          name: '50%',
          text: this._t('fontScaleXSmall'),
          param: '1'
        }
      ]
      return FontScaleButton.__super__._init.call(this)
    }

    FontScaleButton.prototype._activeStatus = function() {
      var active, endNode, endNodes, range, startNode, startNodes
      range = this.editor.selection.range()
      startNodes = this.editor.selection.startNodes()
      endNodes = this.editor.selection.endNodes()
      startNode = startNodes.filter('span[style*="font-size"]')
      endNode = endNodes.filter('span[style*="font-size"]')
      active = startNodes.length > 0 && endNodes.length > 0 && startNode.is(endNode)
      this.setActive(active)
      return this.active
    }

    FontScaleButton.prototype.command = function(param) {
      var $scales, containerNode, range
      range = this.editor.selection.range()
      if (range.collapsed) {
        return
      }
      document.execCommand('styleWithCSS', false, true)
      document.execCommand('fontSize', false, param)
      document.execCommand('styleWithCSS', false, false)
      this.editor.selection.reset()
      this.editor.selection.range()
      containerNode = this.editor.selection.containerNode()
      if (containerNode[0].nodeType === Node.TEXT_NODE) {
        $scales = containerNode.closest('span[style*="font-size"]')
      } else {
        $scales = containerNode.find('span[style*="font-size"]')
      }
      $scales.each((function(_this) {
        return function(i, n) {
          var $span, size
          $span = $(n)
          size = n.style.fontSize
          if (/large|x-large|small|x-small/.test(size)) {
            return $span.css('fontSize', _this.sizeMap[size])
          } else if (size === 'medium') {
            return $span.replaceWith($span.contents())
          }
        }
      })(this))
      return this.editor.trigger('valuechanged')
    }

    return FontScaleButton
  })(Button)

  Simditor.Toolbar.addButton(FontScaleButton)

  BoldButton = (function(superClass) {
    extend(BoldButton, superClass)

    function BoldButton() {
      return BoldButton.__super__.constructor.apply(this, arguments)
    }

    BoldButton.prototype.name = 'bold'

    BoldButton.prototype.icon = 'bold'

    BoldButton.prototype.htmlTag = 'b, strong'

    BoldButton.prototype.disableTag = 'pre'

    BoldButton.prototype.shortcut = 'cmd+b'

    BoldButton.prototype._init = function() {
      if (this.editor.util.os.mac) {
        this.title = this.title + ' ( Cmd + b )'
      } else {
        this.title = this.title + ' ( Ctrl + b )'
        this.shortcut = 'ctrl+b'
      }
      return BoldButton.__super__._init.call(this)
    }

    BoldButton.prototype._activeStatus = function() {
      var active
      active = document.queryCommandState('bold') === true
      this.setActive(active)
      return this.active
    }

    BoldButton.prototype.command = function() {
      document.execCommand('bold')
      if (!this.editor.util.support.oninput) {
        this.editor.trigger('valuechanged')
      }
      return $(document).trigger('selectionchange')
    }

    return BoldButton
  })(Button)

  Simditor.Toolbar.addButton(BoldButton)

  ItalicButton = (function(superClass) {
    extend(ItalicButton, superClass)

    function ItalicButton() {
      return ItalicButton.__super__.constructor.apply(this, arguments)
    }

    ItalicButton.prototype.name = 'italic'

    ItalicButton.prototype.icon = 'italic'

    ItalicButton.prototype.htmlTag = 'i'

    ItalicButton.prototype.disableTag = 'pre'

    ItalicButton.prototype.shortcut = 'cmd+i'

    ItalicButton.prototype._init = function() {
      if (this.editor.util.os.mac) {
        this.title = this.title + ' ( Cmd + i )'
      } else {
        this.title = this.title + ' ( Ctrl + i )'
        this.shortcut = 'ctrl+i'
      }
      return ItalicButton.__super__._init.call(this)
    }

    ItalicButton.prototype._activeStatus = function() {
      var active
      active = document.queryCommandState('italic') === true
      this.setActive(active)
      return this.active
    }

    ItalicButton.prototype.command = function() {
      document.execCommand('italic')
      if (!this.editor.util.support.oninput) {
        this.editor.trigger('valuechanged')
      }
      return $(document).trigger('selectionchange')
    }

    return ItalicButton
  })(Button)

  Simditor.Toolbar.addButton(ItalicButton)

  UnderlineButton = (function(superClass) {
    extend(UnderlineButton, superClass)

    function UnderlineButton() {
      return UnderlineButton.__super__.constructor.apply(this, arguments)
    }

    UnderlineButton.prototype.name = 'underline'

    UnderlineButton.prototype.icon = 'underline'

    UnderlineButton.prototype.htmlTag = 'u'

    UnderlineButton.prototype.disableTag = 'pre'

    UnderlineButton.prototype.shortcut = 'cmd+u'

    UnderlineButton.prototype.render = function() {
      if (this.editor.util.os.mac) {
        this.title = this.title + ' ( Cmd + u )'
      } else {
        this.title = this.title + ' ( Ctrl + u )'
        this.shortcut = 'ctrl+u'
      }
      return UnderlineButton.__super__.render.call(this)
    }

    UnderlineButton.prototype._activeStatus = function() {
      var active
      active = document.queryCommandState('underline') === true
      this.setActive(active)
      return this.active
    }

    UnderlineButton.prototype.command = function() {
      document.execCommand('underline')
      if (!this.editor.util.support.oninput) {
        this.editor.trigger('valuechanged')
      }
      return $(document).trigger('selectionchange')
    }

    return UnderlineButton
  })(Button)

  Simditor.Toolbar.addButton(UnderlineButton)

  ColorButton = (function(superClass) {
    extend(ColorButton, superClass)

    function ColorButton() {
      return ColorButton.__super__.constructor.apply(this, arguments)
    }

    ColorButton.prototype.name = 'color'

    ColorButton.prototype.icon = 'tint'

    ColorButton.prototype.disableTag = 'pre'

    ColorButton.prototype.menu = true

    ColorButton.prototype.render = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      return ColorButton.__super__.render.apply(this, args)
    }

    ColorButton.prototype.renderMenu = function() {
      $('<ul class="color-list">\n  <li><a href="javascript:;" class="font-color font-color-1"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-2"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-3"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-4"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-5"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-6"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-7"></a></li>\n  <li><a href="javascript:;" class="font-color font-color-default"></a></li>\n</ul>').appendTo(this.menuWrapper)
      this.menuWrapper.on('mousedown', '.color-list', function(e) {
        return false
      })
      return this.menuWrapper.on('click', '.font-color', (function(_this) {
        return function(e) {
          var $link, $p, hex, range, rgb, textNode
          _this.wrapper.removeClass('menu-on')
          $link = $(e.currentTarget)
          if ($link.hasClass('font-color-default')) {
            $p = _this.editor.body.find('p, li')
            if (!($p.length > 0)) {
              return
            }
            rgb = window.getComputedStyle($p[0], null).getPropertyValue('color')
            hex = _this._convertRgbToHex(rgb)
          } else {
            rgb = window.getComputedStyle($link[0], null).getPropertyValue('background-color')
            hex = _this._convertRgbToHex(rgb)
          }
          if (!hex) {
            return
          }
          range = _this.editor.selection.range()
          if (!$link.hasClass('font-color-default') && range.collapsed) {
            textNode = document.createTextNode(_this._t('coloredText'))
            range.insertNode(textNode)
            range.selectNodeContents(textNode)
            _this.editor.selection.range(range)
          }
          document.execCommand('styleWithCSS', false, true)
          document.execCommand('foreColor', false, hex)
          document.execCommand('styleWithCSS', false, false)
          if (!_this.editor.util.support.oninput) {
            return _this.editor.trigger('valuechanged')
          }
        }
      })(this))
    }

    ColorButton.prototype._convertRgbToHex = function(rgb) {
      var match, re, rgbToHex
      re = /rgb\((\d+),\s?(\d+),\s?(\d+)\)/g
      match = re.exec(rgb)
      if (!match) {
        return ''
      }
      rgbToHex = function(r, g, b) {
        var componentToHex
        componentToHex = function(c) {
          var hex
          hex = c.toString(16)
          if (hex.length === 1) {
            return '0' + hex
          } else {
            return hex
          }
        }
        return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b)
      }
      return rgbToHex(match[1] * 1, match[2] * 1, match[3] * 1)
    }

    return ColorButton
  })(Button)

  Simditor.Toolbar.addButton(ColorButton)

  ListButton = (function(superClass) {
    extend(ListButton, superClass)

    function ListButton() {
      return ListButton.__super__.constructor.apply(this, arguments)
    }

    ListButton.prototype.type = ''

    ListButton.prototype.disableTag = 'pre, table'

    ListButton.prototype.command = function(param) {
      var $list, $rootNodes, anotherType
      $rootNodes = this.editor.selection.blockNodes()
      anotherType = this.type === 'ul' ? 'ol' : 'ul'
      this.editor.selection.save()
      $list = null
      $rootNodes.each((function(_this) {
        return function(i, node) {
          var $node
          $node = $(node)
          if ($node.is('blockquote, li') || $node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node) || !$.contains(document, node)) {
            return
          }
          if ($node.is(_this.type)) {
            $node.children('li').each(function(i, li) {
              var $childList, $li
              $li = $(li)
              $childList = $li.children('ul, ol').insertAfter($node)
              return $('<p/>').append($(li).html() || _this.editor.util.phBr).insertBefore($node)
            })
            return $node.remove()
          } else if ($node.is(anotherType)) {
            return $('<' + _this.type + '/>').append($node.contents()).replaceAll($node)
          } else if ($list && $node.prev().is($list)) {
            $('<li/>').append($node.html() || _this.editor.util.phBr).appendTo($list)
            return $node.remove()
          } else {
            $list = $('<' + _this.type + '><li></li></' + _this.type + '>')
            $list.find('li').append($node.html() || _this.editor.util.phBr)
            return $list.replaceAll($node)
          }
        }
      })(this))
      this.editor.selection.restore()
      return this.editor.trigger('valuechanged')
    }

    return ListButton
  })(Button)

  OrderListButton = (function(superClass) {
    extend(OrderListButton, superClass)

    function OrderListButton() {
      return OrderListButton.__super__.constructor.apply(this, arguments)
    }

    OrderListButton.prototype.type = 'ol'

    OrderListButton.prototype.name = 'ol'

    OrderListButton.prototype.icon = 'list-ol'

    OrderListButton.prototype.htmlTag = 'ol'

    OrderListButton.prototype.shortcut = 'cmd+/'

    OrderListButton.prototype._init = function() {
      if (this.editor.util.os.mac) {
        this.title = this.title + ' ( Cmd + / )'
      } else {
        this.title = this.title + ' ( ctrl + / )'
        this.shortcut = 'ctrl+/'
      }
      return OrderListButton.__super__._init.call(this)
    }

    return OrderListButton
  })(ListButton)

  UnorderListButton = (function(superClass) {
    extend(UnorderListButton, superClass)

    function UnorderListButton() {
      return UnorderListButton.__super__.constructor.apply(this, arguments)
    }

    UnorderListButton.prototype.type = 'ul'

    UnorderListButton.prototype.name = 'ul'

    UnorderListButton.prototype.icon = 'list-ul'

    UnorderListButton.prototype.htmlTag = 'ul'

    UnorderListButton.prototype.shortcut = 'cmd+.'

    UnorderListButton.prototype._init = function() {
      if (this.editor.util.os.mac) {
        this.title = this.title + ' ( Cmd + . )'
      } else {
        this.title = this.title + ' ( Ctrl + . )'
        this.shortcut = 'ctrl+.'
      }
      return UnorderListButton.__super__._init.call(this)
    }

    return UnorderListButton
  })(ListButton)

  Simditor.Toolbar.addButton(OrderListButton)

  Simditor.Toolbar.addButton(UnorderListButton)

  BlockquoteButton = (function(superClass) {
    extend(BlockquoteButton, superClass)

    function BlockquoteButton() {
      return BlockquoteButton.__super__.constructor.apply(this, arguments)
    }

    BlockquoteButton.prototype.name = 'blockquote'

    BlockquoteButton.prototype.icon = 'quote-left'

    BlockquoteButton.prototype.htmlTag = 'blockquote'

    BlockquoteButton.prototype.disableTag = 'pre, table'

    BlockquoteButton.prototype.command = function() {
      var $rootNodes, clearCache, nodeCache
      $rootNodes = this.editor.selection.rootNodes()
      $rootNodes = $rootNodes.filter(function(i, node) {
        return !$(node).parent().is('blockquote')
      })
      this.editor.selection.save()
      nodeCache = []
      clearCache = (function(_this) {
        return function() {
          if (nodeCache.length > 0) {
            $('<' + _this.htmlTag + '/>').insertBefore(nodeCache[0]).append(nodeCache)
            return nodeCache.length = 0
          }
        }
      })(this)
      $rootNodes.each((function(_this) {
        return function(i, node) {
          var $node
          $node = $(node)
          if (!$node.parent().is(_this.editor.body)) {
            return
          }
          if ($node.is(_this.htmlTag)) {
            clearCache()
            return $node.children().unwrap()
          } else if ($node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node)) {
            return clearCache()
          } else {
            return nodeCache.push(node)
          }
        }
      })(this))
      clearCache()
      this.editor.selection.restore()
      return this.editor.trigger('valuechanged')
    }

    return BlockquoteButton
  })(Button)

  Simditor.Toolbar.addButton(BlockquoteButton)

  CodeButton = (function(superClass) {
    extend(CodeButton, superClass)

    function CodeButton() {
      return CodeButton.__super__.constructor.apply(this, arguments)
    }

    CodeButton.prototype.name = 'code'

    CodeButton.prototype.icon = 'code'

    CodeButton.prototype.htmlTag = 'pre'

    CodeButton.prototype.disableTag = 'ul, ol, table'

    CodeButton.prototype._init = function() {
      CodeButton.__super__._init.call(this)
      this.editor.on('decorate', (function(_this) {
        return function(e, $el) {
          return $el.find('pre').each(function(i, pre) {
            return _this.decorate($(pre))
          })
        }
      })(this))
      return this.editor.on('undecorate', (function(_this) {
        return function(e, $el) {
          return $el.find('pre').each(function(i, pre) {
            return _this.undecorate($(pre))
          })
        }
      })(this))
    }

    CodeButton.prototype.render = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      CodeButton.__super__.render.apply(this, args)
      return this.popover = new CodePopover({
        button: this
      })
    }

    CodeButton.prototype._checkMode = function() {
      var $blockNodes, range
      range = this.editor.selection.range()
      if (($blockNodes = $(range.cloneContents()).find(this.editor.util.blockNodes.join(','))) > 0 || (range.collapsed && this.editor.selection.startNodes().filter('code').length === 0)) {
        this.inlineMode = false
        return this.htmlTag = 'pre'
      } else {
        this.inlineMode = true
        return this.htmlTag = 'code'
      }
    }

    CodeButton.prototype._status = function() {
      this._checkMode()
      CodeButton.__super__._status.call(this)
      if (this.inlineMode) {
        return
      }
      if (this.active) {
        return this.popover.show(this.node)
      } else {
        return this.popover.hide()
      }
    }

    CodeButton.prototype.decorate = function($pre) {
      var $code, lang, ref, ref1
      $code = $pre.find('> code')
      if ($code.length > 0) {
        lang = (ref = $code.attr('class')) != null ? (ref1 = ref.match(/lang-(\S+)/)) != null ? ref1[1] : void 0 : void 0
        $code.contents().unwrap()
        if (lang) {
          return $pre.attr('data-lang', lang)
        }
      }
    }

    CodeButton.prototype.undecorate = function($pre) {
      var $code, lang
      lang = $pre.attr('data-lang')
      $code = $('<code/>')
      if (lang && lang !== -1) {
        $code.addClass('lang-' + lang)
      }
      return $pre.wrapInner($code).removeAttr('data-lang')
    }

    CodeButton.prototype.command = function() {
      if (this.inlineMode) {
        return this._inlineCommand()
      } else {
        return this._blockCommand()
      }
    }

    CodeButton.prototype._blockCommand = function() {
      var $rootNodes, clearCache, nodeCache, resultNodes
      $rootNodes = this.editor.selection.rootNodes()
      nodeCache = []
      resultNodes = []
      clearCache = (function(_this) {
        return function() {
          var $pre
          if (!(nodeCache.length > 0)) {
            return
          }
          $pre = $('<' + _this.htmlTag + '/>').insertBefore(nodeCache[0]).text(_this.editor.formatter.clearHtml(nodeCache))
          resultNodes.push($pre[0])
          return nodeCache.length = 0
        }
      })(this)
      $rootNodes.each((function(_this) {
        return function(i, node) {
          var $node, $p
          $node = $(node)
          if ($node.is(_this.htmlTag)) {
            clearCache()
            $p = $('<p/>').append($node.html().replace('\n', '<br/>')).replaceAll($node)
            return resultNodes.push($p[0])
          } else if ($node.is(_this.disableTag) || _this.editor.util.isDecoratedNode($node) || $node.is('blockquote')) {
            return clearCache()
          } else {
            return nodeCache.push(node)
          }
        }
      })(this))
      clearCache()
      this.editor.selection.setRangeAtEndOf($(resultNodes).last())
      return this.editor.trigger('valuechanged')
    }

    CodeButton.prototype._inlineCommand = function() {
      var $code, $contents, range
      range = this.editor.selection.range()
      if (this.active) {
        range.selectNodeContents(this.node[0])
        this.editor.selection.save(range)
        this.node.contents().unwrap()
        this.editor.selection.restore()
      } else {
        $contents = $(range.extractContents())
        $code = $('<' + this.htmlTag + '/>').append($contents.contents())
        range.insertNode($code[0])
        range.selectNodeContents($code[0])
        this.editor.selection.range(range)
      }
      return this.editor.trigger('valuechanged')
    }

    return CodeButton
  })(Button)

  CodePopover = (function(superClass) {
    extend(CodePopover, superClass)

    function CodePopover() {
      return CodePopover.__super__.constructor.apply(this, arguments)
    }

    CodePopover.prototype.render = function() {
      var $option, k, lang, len, ref
      this._tpl = '<div class="code-settings">\n  <div class="settings-field">\n    <select class="select-lang">\n      <option value="-1">' + (this._t('selectLanguage')) + '</option>\n    </select>\n  </div>\n</div>'
      this.langs = this.editor.opts.codeLanguages || [
        {
          name: 'Bash',
          value: 'bash'
        }, {
          name: 'C++',
          value: 'c++'
        }, {
          name: 'C#',
          value: 'cs'
        }, {
          name: 'CSS',
          value: 'css'
        }, {
          name: 'Erlang',
          value: 'erlang'
        }, {
          name: 'Less',
          value: 'less'
        }, {
          name: 'Sass',
          value: 'sass'
        }, {
          name: 'Diff',
          value: 'diff'
        }, {
          name: 'CoffeeScript',
          value: 'coffeescript'
        }, {
          name: 'HTML,XML',
          value: 'html'
        }, {
          name: 'JSON',
          value: 'json'
        }, {
          name: 'Java',
          value: 'java'
        }, {
          name: 'JavaScript',
          value: 'js'
        }, {
          name: 'Markdown',
          value: 'markdown'
        }, {
          name: 'Objective C',
          value: 'oc'
        }, {
          name: 'PHP',
          value: 'php'
        }, {
          name: 'Perl',
          value: 'parl'
        }, {
          name: 'Python',
          value: 'python'
        }, {
          name: 'Ruby',
          value: 'ruby'
        }, {
          name: 'SQL',
          value: 'sql'
        }
      ]
      this.el.addClass('code-popover').append(this._tpl)
      this.selectEl = this.el.find('.select-lang')
      ref = this.langs
      for (k = 0, len = ref.length; k < len; k++) {
        lang = ref[k]
        $option = $('<option/>', {
          text: lang.name,
          value: lang.value
        }).appendTo(this.selectEl)
      }
      this.selectEl.on('change', (function(_this) {
        return function(e) {
          var selected
          _this.lang = _this.selectEl.val()
          selected = _this.target.hasClass('selected')
          _this.target.removeClass().removeAttr('data-lang')
          if (_this.lang !== -1) {
            _this.target.attr('data-lang', _this.lang)
          }
          if (selected) {
            _this.target.addClass('selected')
          }
          return _this.editor.trigger('valuechanged')
        }
      })(this))
      return this.editor.on('valuechanged', (function(_this) {
        return function(e) {
          if (_this.active) {
            return _this.refresh()
          }
        }
      })(this))
    }

    CodePopover.prototype.show = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      CodePopover.__super__.show.apply(this, args)
      this.lang = this.target.attr('data-lang')
      if (this.lang != null) {
        return this.selectEl.val(this.lang)
      } else {
        return this.selectEl.val(-1)
      }
    }

    return CodePopover
  })(Popover)

  Simditor.Toolbar.addButton(CodeButton)

  LinkButton = (function(superClass) {
    extend(LinkButton, superClass)

    function LinkButton() {
      return LinkButton.__super__.constructor.apply(this, arguments)
    }

    LinkButton.prototype.name = 'link'

    LinkButton.prototype.icon = 'link'

    LinkButton.prototype.htmlTag = 'a'

    LinkButton.prototype.disableTag = 'pre'

    LinkButton.prototype.render = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      LinkButton.__super__.render.apply(this, args)
      return this.popover = new LinkPopover({
        button: this
      })
    }

    LinkButton.prototype._status = function() {
      LinkButton.__super__._status.call(this)
      if (this.active && !this.editor.selection.rangeAtEndOf(this.node)) {
        return this.popover.show(this.node)
      } else {
        return this.popover.hide()
      }
    }

    LinkButton.prototype.command = function() {
      var $contents, $link, $newBlock, linkText, range, txtNode
      range = this.editor.selection.range()
      if (this.active) {
        txtNode = document.createTextNode(this.node.text())
        this.node.replaceWith(txtNode)
        range.selectNode(txtNode)
      } else {
        $contents = $(range.extractContents())
        linkText = this.editor.formatter.clearHtml($contents.contents(), false)
        $link = $('<a/>', {
          href: 'http://www.example.com',
          target: '_blank',
          text: linkText || this._t('linkText')
        })
        if (this.editor.selection.blockNodes().length > 0) {
          range.insertNode($link[0])
        } else {
          $newBlock = $('<p/>').append($link)
          range.insertNode($newBlock[0])
        }
        range.selectNodeContents($link[0])
        this.popover.one('popovershow', (function(_this) {
          return function() {
            if (linkText) {
              _this.popover.urlEl.focus()
              return _this.popover.urlEl[0].select()
            } else {
              _this.popover.textEl.focus()
              return _this.popover.textEl[0].select()
            }
          }
        })(this))
      }
      this.editor.selection.range(range)
      return this.editor.trigger('valuechanged')
    }

    return LinkButton
  })(Button)

  LinkPopover = (function(superClass) {
    extend(LinkPopover, superClass)

    function LinkPopover() {
      return LinkPopover.__super__.constructor.apply(this, arguments)
    }

    LinkPopover.prototype.render = function() {
      var tpl
      tpl = '<div class="link-settings">\n  <div class="settings-field">\n    <label>' + (this._t('linkText')) + '</label>\n    <input class="link-text" type="text"/>\n    <a class="btn-unlink" href="javascript:;" title="' + (this._t('removeLink')) + '"\n      tabindex="-1">\n      <span class="simditor-icon simditor-icon-unlink"></span>\n    </a>\n  </div>\n  <div class="settings-field">\n    <label>' + (this._t('linkUrl')) + '</label>\n    <input class="link-url" type="text"/>\n  </div>\n  <div class="settings-field">\n    <label>' + (this._t('linkTarget')) + '</label>\n    <select class="link-target">\n      <option value="_blank">' + (this._t('openLinkInNewWindow')) + ' (_blank)</option>\n      <option value="_self">' + (this._t('openLinkInCurrentWindow')) + ' (_self)</option>\n    </select>\n  </div>\n</div>'
      this.el.addClass('link-popover').append(tpl)
      this.textEl = this.el.find('.link-text')
      this.urlEl = this.el.find('.link-url')
      this.unlinkEl = this.el.find('.btn-unlink')
      this.selectTarget = this.el.find('.link-target')
      this.textEl.on('keyup', (function(_this) {
        return function(e) {
          if (e.which === 13) {
            return
          }
          _this.target.text(_this.textEl.val())
          return _this.editor.inputManager.throttledValueChanged()
        }
      })(this))
      this.urlEl.on('keyup', (function(_this) {
        return function(e) {
          var val
          if (e.which === 13) {
            return
          }
          val = _this.urlEl.val()
          if (!(/https?:\/\/|^\//ig.test(val) || !val)) {
            val = 'http://' + val
          }
          _this.target.attr('href', val)
          return _this.editor.inputManager.throttledValueChanged()
        }
      })(this))
      $([this.urlEl[0], this.textEl[0]]).on('keydown', (function(_this) {
        return function(e) {
          var range
          if (e.which === 13 || e.which === 27 || (!e.shiftKey && e.which === 9 && $(e.target).hasClass('link-url'))) {
            e.preventDefault()
            range = document.createRange()
            _this.editor.selection.setRangeAfter(_this.target, range)
            _this.hide()
            return _this.editor.inputManager.throttledValueChanged()
          }
        }
      })(this))
      this.unlinkEl.on('click', (function(_this) {
        return function(e) {
          var range, txtNode
          txtNode = document.createTextNode(_this.target.text())
          _this.target.replaceWith(txtNode)
          _this.hide()
          range = document.createRange()
          _this.editor.selection.setRangeAfter(txtNode, range)
          return _this.editor.inputManager.throttledValueChanged()
        }
      })(this))
      return this.selectTarget.on('change', (function(_this) {
        return function(e) {
          _this.target.attr('target', _this.selectTarget.val())
          return _this.editor.inputManager.throttledValueChanged()
        }
      })(this))
    }

    LinkPopover.prototype.show = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      LinkPopover.__super__.show.apply(this, args)
      this.textEl.val(this.target.text())
      return this.urlEl.val(this.target.attr('href'))
    }

    return LinkPopover
  })(Popover)

  Simditor.Toolbar.addButton(LinkButton)

  ImageButton = (function(superClass) {
    extend(ImageButton, superClass)

    function ImageButton() {
      return ImageButton.__super__.constructor.apply(this, arguments)
    }

    ImageButton.prototype.name = 'image'

    ImageButton.prototype.icon = 'picture-o'

    ImageButton.prototype.htmlTag = 'img'

    ImageButton.prototype.disableTag = 'pre, table'

    ImageButton.prototype.defaultImage = ''

    ImageButton.prototype.needFocus = false

    ImageButton.prototype._init = function() {
      var item, k, len, ref
      if (this.editor.opts.imageButton) {
        if (Array.isArray(this.editor.opts.imageButton)) {
          this.menu = []
          ref = this.editor.opts.imageButton
          for (k = 0, len = ref.length; k < len; k++) {
            item = ref[k]
            this.menu.push({
              name: item + '-image',
              text: this._t(item + 'Image')
            })
          }
        } else {
          this.menu = false
        }
      } else {
        if (this.editor.uploader != null) {
          this.menu = [
            {
              name: 'upload-image',
              text: this._t('uploadImage')
            }, {
              name: 'external-image',
              text: this._t('externalImage')
            }
          ]
        } else {
          this.menu = false
        }
      }
      this.defaultImage = this.editor.opts.defaultImage
      this.editor.body.on('click', 'img:not([data-non-image])', (function(_this) {
        return function(e) {
          var $img, range
          $img = $(e.currentTarget)
          range = document.createRange()
          range.selectNode($img[0])
          _this.editor.selection.range(range)
          if (!_this.editor.util.support.onselectionchange) {
            _this.editor.trigger('selectionchanged')
          }
          return false
        }
      })(this))
      this.editor.body.on('mouseup', 'img:not([data-non-image])', function(e) {
        return false
      })
      this.editor.on('selectionchanged.image', (function(_this) {
        return function() {
          var $contents, $img, range
          range = _this.editor.selection.range()
          if (range == null) {
            return
          }
          $contents = $(range.cloneContents()).contents()
          if ($contents.length === 1 && $contents.is('img:not([data-non-image])')) {
            $img = $(range.startContainer).contents().eq(range.startOffset)
            return _this.popover.show($img)
          } else {
            return _this.popover.hide()
          }
        }
      })(this))
      this.editor.on('valuechanged.image', (function(_this) {
        return function() {
          var $masks
          $masks = _this.editor.wrapper.find('.simditor-image-loading')
          if (!($masks.length > 0)) {
            return
          }
          return $masks.each(function(i, mask) {
            var $img, $mask, file
            $mask = $(mask)
            $img = $mask.data('img')
            if (!($img && $img.parent().length > 0)) {
              $mask.remove()
              if ($img) {
                file = $img.data('file')
                if (file) {
                  _this.editor.uploader.cancel(file)
                  if (_this.editor.body.find('img.uploading').length < 1) {
                    return _this.editor.uploader.trigger('uploadready', [file])
                  }
                }
              }
            }
          })
        }
      })(this))
      return ImageButton.__super__._init.call(this)
    }

    ImageButton.prototype.render = function() {
      var args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      ImageButton.__super__.render.apply(this, args)
      this.popover = new ImagePopover({
        button: this
      })
      if (this.editor.opts.imageButton === 'upload') {
        return this._initUploader(this.el)
      }
    }

    ImageButton.prototype.renderMenu = function() {
      ImageButton.__super__.renderMenu.call(this)
      return this._initUploader()
    }

    ImageButton.prototype._initUploader = function($uploadItem) {
      var $input, createInput, uploadProgress
      if ($uploadItem == null) {
        $uploadItem = this.menuEl.find('.menu-item-upload-image')
      }
      if (this.editor.uploader == null) {
        this.el.find('.btn-upload').remove()
        return
      }
      $input = null
      createInput = (function(_this) {
        return function() {
          if ($input) {
            $input.remove()
          }
          return $input = $('<input/>', {
            type: 'file',
            title: _this._t('uploadImage'),
            multiple: true,
            accept: 'image/*',
            name: 'upload_file'
          }).appendTo($uploadItem)
        }
      })(this)
      createInput()
      $uploadItem.on('click mousedown', 'input[type=file]', function(e) {
        return e.stopPropagation()
      })
      $uploadItem.on('change', 'input[type=file]', (function(_this) {
        return function(e) {
          if (_this.editor.inputManager.focused) {
            _this.editor.uploader.upload($input, {
              inline: true
            })
            createInput()
          } else {
            _this.editor.one('focus', function(e) {
              _this.editor.uploader.upload($input, {
                inline: true
              })
              return createInput()
            })
            _this.editor.focus()
          }
          return _this.wrapper.removeClass('menu-on')
        }
      })(this))
      this.editor.uploader.on('beforeupload', (function(_this) {
        return function(e, file) {
          var $img
          if (!file.inline) {
            return
          }
          if (file.img) {
            $img = $(file.img)
          } else {
            $img = _this.createImage(file.name)
            file.img = $img
          }
          $img.addClass('uploading')
          $img.data('file', file)
          return _this.editor.uploader.readImageFile(file.obj, function(img) {
            var src
            if (!$img.hasClass('uploading')) {
              return
            }
            src = img ? img.src : _this.defaultImage
            return _this.loadImage($img, src, function() {
              if (_this.popover.active) {
                _this.popover.refresh()
                return _this.popover.srcEl.val(_this._t('uploading')).prop('disabled', true)
              }
            })
          })
        }
      })(this))
      uploadProgress = $.proxy(this.editor.util.throttle(function(e, file, loaded, total) {
        var $img, $mask, percent
        if (!file.inline) {
          return
        }
        $mask = file.img.data('mask')
        if (!$mask) {
          return
        }
        $img = $mask.data('img')
        if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
          $mask.remove()
          return
        }
        percent = loaded / total
        percent = (percent * 100).toFixed(0)
        if (percent > 99) {
          percent = 99
        }
        return $mask.find('.progress').height((100 - percent) + '%')
      }, 500), this)
      this.editor.uploader.on('uploadprogress', uploadProgress)
      this.editor.uploader.on('uploadsuccess', (function(_this) {
        return function(e, file, result) {
          var $img, img_path, msg
          if (!file.inline) {
            return
          }
          $img = file.img
          if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
            return
          }
          if (typeof result !== 'object') {
            try {
              result = $.parseJSON(result)
            } catch (_error) {
              e = _error
              result = {
                success: false
              }
            }
          }
          if (result.success === false) {
            msg = result.msg || _this._t('uploadFailed')
            alert(msg + '1111')
            img_path = _this.defaultImage
          } else {
            console.log('sdfsdfsdf')
            img_path = result.file_path
          }
          _this.loadImage($img, img_path, function() {
            var $mask
            $img.removeData('file')
            $img.removeClass('uploading').removeClass('loading')
            $mask = $img.data('mask')
            if ($mask) {
              $mask.remove()
            }
            $img.removeData('mask')
            _this.editor.trigger('valuechanged')
            if (_this.editor.body.find('img.uploading').length < 1) {
              return _this.editor.uploader.trigger('uploadready', [file, result])
            }
          })
          if (_this.popover.active) {
            _this.popover.srcEl.prop('disabled', false)
            return _this.popover.srcEl.val(result.file_path)
          }
        }
      })(this))
      return this.editor.uploader.on('uploaderror', (function(_this) {
        return function(e, file, xhr) {
          var $img, msg, result
          if (!file.inline) {
            return
          }
          if (xhr.statusText === 'abort') {
            return
          }
          if (xhr.responseText) {
            try {
              result = $.parseJSON(xhr.responseText)
              msg = result.msg
            } catch (_error) {
              e = _error
              msg = _this._t('uploadError')
            }
            alert(msg + '222')
          }
          $img = file.img
          if (!($img.hasClass('uploading') && $img.parent().length > 0)) {
            return
          }
          _this.loadImage($img, _this.defaultImage, function() {
            var $mask
            $img.removeData('file')
            $img.removeClass('uploading').removeClass('loading')
            $mask = $img.data('mask')
            if ($mask) {
              $mask.remove()
            }
            return $img.removeData('mask')
          })
          if (_this.popover.active) {
            _this.popover.srcEl.prop('disabled', false)
            _this.popover.srcEl.val(_this.defaultImage)
          }
          _this.editor.trigger('valuechanged')
          if (_this.editor.body.find('img.uploading').length < 1) {
            return _this.editor.uploader.trigger('uploadready', [file, result])
          }
        }
      })(this))
    }

    ImageButton.prototype._status = function() {
      return this._disableStatus()
    }

    ImageButton.prototype.loadImage = function($img, src, callback) {
      var $mask, img, positionMask
      positionMask = (function(_this) {
        return function() {
          var imgOffset, wrapperOffset
          imgOffset = $img.offset()
          wrapperOffset = _this.editor.wrapper.offset()
          return $mask.css({
            top: imgOffset.top - wrapperOffset.top,
            left: imgOffset.left - wrapperOffset.left,
            width: $img.width(),
            height: $img.height()
          }).show()
        }
      })(this)
      $img.addClass('loading')
      $mask = $img.data('mask')
      if (!$mask) {
        $mask = $('<div class="simditor-image-loading">\n  <div class="progress"></div>\n</div>').hide().appendTo(this.editor.wrapper)
        positionMask()
        $img.data('mask', $mask)
        $mask.data('img', $img)
      }
      img = new Image()
      img.onload = (function(_this) {
        return function() {
          var height, width
          if (!$img.hasClass('loading') && !$img.hasClass('uploading')) {
            return
          }
          width = img.width
          height = img.height
          $img.attr({
            src: src,
            width: width,
            height: height,
            'data-image-size': width + ',' + height
          }).removeClass('loading')
          if ($img.hasClass('uploading')) {
            _this.editor.util.reflow(_this.editor.body)
            positionMask()
          } else {
            $mask.remove()
            $img.removeData('mask')
          }
          if ($.isFunction(callback)) {
            return callback(img)
          }
        }
      })(this)
      img.onerror = function() {
        if ($.isFunction(callback)) {
          callback(false)
        }
        $mask.remove()
        return $img.removeData('mask').removeClass('loading')
      }
      return img.src = src
    }

    ImageButton.prototype.createImage = function(name) {
      var $img, range
      if (name == null) {
        name = 'Image'
      }
      if (!this.editor.inputManager.focused) {
        this.editor.focus()
      }
      range = this.editor.selection.range()
      range.deleteContents()
      this.editor.selection.range(range)
      $img = $('<img/>').attr('alt', name)
      range.insertNode($img[0])
      this.editor.selection.setRangeAfter($img, range)
      this.editor.trigger('valuechanged')
      return $img
    }

    ImageButton.prototype.command = function(src) {
      var $img
      $img = this.createImage()
      return this.loadImage($img, src || this.defaultImage, (function(_this) {
        return function() {
          _this.editor.trigger('valuechanged')
          _this.editor.util.reflow($img)
          $img.click()
          return _this.popover.one('popovershow', function() {
            _this.popover.srcEl.focus()
            return _this.popover.srcEl[0].select()
          })
        }
      })(this))
    }

    return ImageButton
  })(Button)

  ImagePopover = (function(superClass) {
    extend(ImagePopover, superClass)

    function ImagePopover() {
      return ImagePopover.__super__.constructor.apply(this, arguments)
    }

    ImagePopover.prototype.offset = {
      top: 6,
      left: -4
    }

    ImagePopover.prototype.render = function() {
      var tpl
      tpl = '<div class="link-settings">\n  <div class="settings-field">\n    <label>' + (this._t('imageUrl')) + '</label>\n    <input class="image-src" type="text" tabindex="1" />\n    <a class="btn-upload" href="javascript:;"\n      title="' + (this._t('uploadImage')) + "\" tabindex=\"-1\">\n      <span class=\"simditor-icon simditor-icon-upload\"></span>\n    </a>\n  </div>\n  <div class='settings-field'>\n    <label>" + (this._t('imageAlt')) + '</label>\n    <input class="image-alt" id="image-alt" type="text" tabindex="1" />\n  </div>\n  <div class="settings-field">\n    <label>' + (this._t('imageSize')) + '</label>\n    <input class="image-size" id="image-width" type="text" tabindex="2" />\n    <span class="times">×</span>\n    <input class="image-size" id="image-height" type="text" tabindex="3" />\n    <a class="btn-restore" href="javascript:;"\n      title="' + (this._t('restoreImageSize')) + '" tabindex="-1">\n      <span class="simditor-icon simditor-icon-undo"></span>\n    </a>\n  </div>\n</div>'
      this.el.addClass('image-popover').append(tpl)
      this.srcEl = this.el.find('.image-src')
      this.widthEl = this.el.find('#image-width')
      this.heightEl = this.el.find('#image-height')
      this.altEl = this.el.find('#image-alt')
      this.srcEl.on('keydown', (function(_this) {
        return function(e) {
          var range
          if (!(e.which === 13 && !_this.target.hasClass('uploading'))) {
            return
          }
          e.preventDefault()
          range = document.createRange()
          _this.button.editor.selection.setRangeAfter(_this.target, range)
          return _this.hide()
        }
      })(this))
      this.srcEl.on('blur', (function(_this) {
        return function(e) {
          return _this._loadImage(_this.srcEl.val())
        }
      })(this))
      this.el.find('.image-size').on('blur', (function(_this) {
        return function(e) {
          _this._resizeImg($(e.currentTarget))
          return _this.el.data('popover').refresh()
        }
      })(this))
      this.el.find('.image-size').on('keyup', (function(_this) {
        return function(e) {
          var inputEl
          inputEl = $(e.currentTarget)
          if (!(e.which === 13 || e.which === 27 || e.which === 9)) {
            return _this._resizeImg(inputEl, true)
          }
        }
      })(this))
      this.el.find('.image-size').on('keydown', (function(_this) {
        return function(e) {
          var $img, inputEl, range
          inputEl = $(e.currentTarget)
          if (e.which === 13 || e.which === 27) {
            e.preventDefault()
            if (e.which === 13) {
              _this._resizeImg(inputEl)
            } else {
              _this._restoreImg()
            }
            $img = _this.target
            _this.hide()
            range = document.createRange()
            return _this.button.editor.selection.setRangeAfter($img, range)
          } else if (e.which === 9) {
            return _this.el.data('popover').refresh()
          }
        }
      })(this))
      this.altEl.on('keydown', (function(_this) {
        return function(e) {
          var range
          if (e.which === 13) {
            e.preventDefault()
            range = document.createRange()
            _this.button.editor.selection.setRangeAfter(_this.target, range)
            return _this.hide()
          }
        }
      })(this))
      this.altEl.on('keyup', (function(_this) {
        return function(e) {
          if (e.which === 13 || e.which === 27 || e.which === 9) {
            return
          }
          _this.alt = _this.altEl.val()
          return _this.target.attr('alt', _this.alt)
        }
      })(this))
      this.el.find('.btn-restore').on('click', (function(_this) {
        return function(e) {
          _this._restoreImg()
          return _this.el.data('popover').refresh()
        }
      })(this))
      this.editor.on('valuechanged', (function(_this) {
        return function(e) {
          if (_this.active) {
            return _this.refresh()
          }
        }
      })(this))
      return this._initUploader()
    }

    ImagePopover.prototype._initUploader = function() {
      var $uploadBtn, createInput
      $uploadBtn = this.el.find('.btn-upload')
      if (this.editor.uploader == null) {
        $uploadBtn.remove()
        return
      }
      createInput = (function(_this) {
        return function() {
          if (_this.input) {
            _this.input.remove()
          }
          return _this.input = $('<input/>', {
            type: 'file',
            title: _this._t('uploadImage'),
            multiple: true,
            accept: 'image/*',
            name: 'upload_file'
          }).appendTo($uploadBtn)
        }
      })(this)
      createInput()
      this.el.on('click mousedown', 'input[type=file]', function(e) {
        return e.stopPropagation()
      })
      return this.el.on('change', 'input[type=file]', (function(_this) {
        return function(e) {
          _this.editor.uploader.upload(_this.input, {
            inline: true,
            img: _this.target
          })
          return createInput()
        }
      })(this))
    }

    ImagePopover.prototype._resizeImg = function(inputEl, onlySetVal) {
      var height, value, width
      if (onlySetVal == null) {
        onlySetVal = false
      }
      value = inputEl.val() * 1
      if (!(this.target && ($.isNumeric(value) || value < 0))) {
        return
      }
      if (inputEl.is(this.widthEl)) {
        width = value
        height = this.height * value / this.width
        this.heightEl.val(height)
      } else {
        height = value
        width = this.width * value / this.height
        this.widthEl.val(width)
      }
      if (!onlySetVal) {
        this.target.attr({
          width: width,
          height: height
        })
        return this.editor.trigger('valuechanged')
      }
    }

    ImagePopover.prototype._restoreImg = function() {
      var ref, size
      size = ((ref = this.target.data('image-size')) != null ? ref.split(',') : void 0) || [this.width, this.height]
      this.target.attr({
        width: size[0] * 1,
        height: size[1] * 1
      })
      this.widthEl.val(size[0])
      this.heightEl.val(size[1])
      return this.editor.trigger('valuechanged')
    }

    ImagePopover.prototype._loadImage = function(src, callback) {
      if (/^data:image/.test(src) && !this.editor.uploader) {
        if (callback) {
          callback(false)
        }
        return
      }
      if (this.target.attr('src') === src) {
        return
      }
      return this.button.loadImage(this.target, src, (function(_this) {
        return function(img) {
          var blob
          if (!img) {
            return
          }
          if (_this.active) {
            _this.width = img.width
            _this.height = img.height
            _this.widthEl.val(_this.width)
            _this.heightEl.val(_this.height)
          }
          if (/^data:image/.test(src)) {
            blob = _this.editor.util.dataURLtoBlob(src)
            blob.name = 'Base64 Image.png'
            _this.editor.uploader.upload(blob, {
              inline: true,
              img: _this.target
            })
          } else {
            _this.editor.trigger('valuechanged')
          }
          if (callback) {
            return callback(img)
          }
        }
      })(this))
    }

    ImagePopover.prototype.show = function() {
      var $img, args
      args = arguments.length >= 1 ? slice.call(arguments, 0) : []
      ImagePopover.__super__.show.apply(this, args)
      $img = this.target
      this.width = $img.width()
      this.height = $img.height()
      this.alt = $img.attr('alt')
      if ($img.hasClass('uploading')) {
        return this.srcEl.val(this._t('uploading')).prop('disabled', true)
      } else {
        this.srcEl.val($img.attr('src')).prop('disabled', false)
        this.widthEl.val(this.width)
        this.heightEl.val(this.height)
        return this.altEl.val(this.alt)
      }
    }

    return ImagePopover
  })(Popover)

  Simditor.Toolbar.addButton(ImageButton)

  IndentButton = (function(superClass) {
    extend(IndentButton, superClass)

    function IndentButton() {
      return IndentButton.__super__.constructor.apply(this, arguments)
    }

    IndentButton.prototype.name = 'indent'

    IndentButton.prototype.icon = 'indent'

    IndentButton.prototype._init = function() {
      this.title = this._t(this.name) + ' (Tab)'
      return IndentButton.__super__._init.call(this)
    }

    IndentButton.prototype._status = function() {}

    IndentButton.prototype.command = function() {
      return this.editor.indentation.indent()
    }

    return IndentButton
  })(Button)

  Simditor.Toolbar.addButton(IndentButton)

  OutdentButton = (function(superClass) {
    extend(OutdentButton, superClass)

    function OutdentButton() {
      return OutdentButton.__super__.constructor.apply(this, arguments)
    }

    OutdentButton.prototype.name = 'outdent'

    OutdentButton.prototype.icon = 'outdent'

    OutdentButton.prototype._init = function() {
      this.title = this._t(this.name) + ' (Shift + Tab)'
      return OutdentButton.__super__._init.call(this)
    }

    OutdentButton.prototype._status = function() {}

    OutdentButton.prototype.command = function() {
      return this.editor.indentation.indent(true)
    }

    return OutdentButton
  })(Button)

  Simditor.Toolbar.addButton(OutdentButton)

  HrButton = (function(superClass) {
    extend(HrButton, superClass)

    function HrButton() {
      return HrButton.__super__.constructor.apply(this, arguments)
    }

    HrButton.prototype.name = 'hr'

    HrButton.prototype.icon = 'minus'

    HrButton.prototype.htmlTag = 'hr'

    HrButton.prototype._status = function() {}

    HrButton.prototype.command = function() {
      var $hr, $newBlock, $nextBlock, $rootBlock
      $rootBlock = this.editor.selection.rootNodes().first()
      $nextBlock = $rootBlock.next()
      if ($nextBlock.length > 0) {
        this.editor.selection.save()
      } else {
        $newBlock = $('<p/>').append(this.editor.util.phBr)
      }
      $hr = $('<hr/>').insertAfter($rootBlock)
      if ($newBlock) {
        $newBlock.insertAfter($hr)
        this.editor.selection.setRangeAtStartOf($newBlock)
      } else {
        this.editor.selection.restore()
      }
      return this.editor.trigger('valuechanged')
    }

    return HrButton
  })(Button)

  Simditor.Toolbar.addButton(HrButton)

  TableButton = (function(superClass) {
    extend(TableButton, superClass)

    function TableButton() {
      return TableButton.__super__.constructor.apply(this, arguments)
    }

    TableButton.prototype.name = 'table'

    TableButton.prototype.icon = 'table'

    TableButton.prototype.htmlTag = 'table'

    TableButton.prototype.disableTag = 'pre, li, blockquote'

    TableButton.prototype.menu = true

    TableButton.prototype._init = function() {
      TableButton.__super__._init.call(this)
      $.merge(this.editor.formatter._allowedTags, ['thead', 'th', 'tbody', 'tr', 'td', 'colgroup', 'col'])
      $.extend(this.editor.formatter._allowedAttributes, {
        td: ['rowspan', 'colspan'],
        col: ['width']
      })
      $.extend(this.editor.formatter._allowedStyles, {
        td: ['text-align'],
        th: ['text-align']
      })
      this._initShortcuts()
      this.editor.on('decorate', (function(_this) {
        return function(e, $el) {
          return $el.find('table').each(function(i, table) {
            return _this.decorate($(table))
          })
        }
      })(this))
      this.editor.on('undecorate', (function(_this) {
        return function(e, $el) {
          return $el.find('table').each(function(i, table) {
            return _this.undecorate($(table))
          })
        }
      })(this))
      this.editor.on('selectionchanged.table', (function(_this) {
        return function(e) {
          var $container, range
          _this.editor.body.find('.simditor-table td, .simditor-table th').removeClass('active')
          range = _this.editor.selection.range()
          if (!range) {
            return
          }
          $container = _this.editor.selection.containerNode()
          if (range.collapsed && $container.is('.simditor-table')) {
            if (_this.editor.selection.rangeAtStartOf($container)) {
              $container = $container.find('th:first')
            } else {
              $container = $container.find('td:last')
            }
            _this.editor.selection.setRangeAtEndOf($container)
          }
          return $container.closest('td, th', _this.editor.body).addClass('active')
        }
      })(this))
      this.editor.on('blur.table', (function(_this) {
        return function(e) {
          return _this.editor.body.find('.simditor-table td, .simditor-table th').removeClass('active')
        }
      })(this))
      this.editor.keystroke.add('up', 'td', (function(_this) {
        return function(e, $node) {
          _this._tdNav($node, 'up')
          return true
        }
      })(this))
      this.editor.keystroke.add('up', 'th', (function(_this) {
        return function(e, $node) {
          _this._tdNav($node, 'up')
          return true
        }
      })(this))
      this.editor.keystroke.add('down', 'td', (function(_this) {
        return function(e, $node) {
          _this._tdNav($node, 'down')
          return true
        }
      })(this))
      return this.editor.keystroke.add('down', 'th', (function(_this) {
        return function(e, $node) {
          _this._tdNav($node, 'down')
          return true
        }
      })(this))
    }

    TableButton.prototype._tdNav = function($td, direction) {
      var $anotherTr, $tr, action, anotherTag, index, parentTag, ref
      if (direction == null) {
        direction = 'up'
      }
      action = direction === 'up' ? 'prev' : 'next'
      ref = direction === 'up' ? ['tbody', 'thead'] : ['thead', 'tbody'], parentTag = ref[0], anotherTag = ref[1]
      $tr = $td.parent('tr')
      $anotherTr = this['_' + action + 'Row']($tr)
      if (!($anotherTr.length > 0)) {
        return true
      }
      index = $tr.find('td, th').index($td)
      return this.editor.selection.setRangeAtEndOf($anotherTr.find('td, th').eq(index))
    }

    TableButton.prototype._nextRow = function($tr) {
      var $nextTr
      $nextTr = $tr.next('tr')
      if ($nextTr.length < 1 && $tr.parent('thead').length > 0) {
        $nextTr = $tr.parent('thead').next('tbody').find('tr:first')
      }
      return $nextTr
    }

    TableButton.prototype._prevRow = function($tr) {
      var $prevTr
      $prevTr = $tr.prev('tr')
      if ($prevTr.length < 1 && $tr.parent('tbody').length > 0) {
        $prevTr = $tr.parent('tbody').prev('thead').find('tr')
      }
      return $prevTr
    }

    TableButton.prototype.initResize = function($table) {
      var $colgroup, $resizeHandle, $wrapper
      $wrapper = $table.parent('.simditor-table')
      $colgroup = $table.find('colgroup')
      if ($colgroup.length < 1) {
        $colgroup = $('<colgroup/>').prependTo($table)
        $table.find('thead tr th').each(function(i, td) {
          var $col
          return $col = $('<col/>').appendTo($colgroup)
        })
        this.refreshTableWidth($table)
      }
      $resizeHandle = $('<div />', {
        'class': 'simditor-resize-handle',
        contenteditable: 'false'
      }).appendTo($wrapper)
      $wrapper.on('mousemove', 'td, th', function(e) {
        var $col, $td, index, ref, ref1, x
        if ($wrapper.hasClass('resizing')) {
          return
        }
        $td = $(e.currentTarget)
        x = e.pageX - $(e.currentTarget).offset().left
        if (x < 5 && $td.prev().length > 0) {
          $td = $td.prev()
        }
        if ($td.next('td, th').length < 1) {
          $resizeHandle.hide()
          return
        }
        if ((ref = $resizeHandle.data('td')) != null ? ref.is($td) : void 0) {
          $resizeHandle.show()
          return
        }
        index = $td.parent().find('td, th').index($td)
        $col = $colgroup.find('col').eq(index)
        if ((ref1 = $resizeHandle.data('col')) != null ? ref1.is($col) : void 0) {
          $resizeHandle.show()
          return
        }
        return $resizeHandle.css('left', $td.position().left + $td.outerWidth() - 5).data('td', $td).data('col', $col).show()
      })
      $wrapper.on('mouseleave', function(e) {
        return $resizeHandle.hide()
      })
      return $wrapper.on('mousedown', '.simditor-resize-handle', function(e) {
        var $handle, $leftCol, $leftTd, $rightCol, $rightTd, minWidth, startHandleLeft, startLeftWidth, startRightWidth, startX, tableWidth
        $handle = $(e.currentTarget)
        $leftTd = $handle.data('td')
        $leftCol = $handle.data('col')
        $rightTd = $leftTd.next('td, th')
        $rightCol = $leftCol.next('col')
        startX = e.pageX
        startLeftWidth = $leftTd.outerWidth() * 1
        startRightWidth = $rightTd.outerWidth() * 1
        startHandleLeft = parseFloat($handle.css('left'))
        tableWidth = $leftTd.closest('table').width()
        minWidth = 50
        $(document).on('mousemove.simditor-resize-table', function(e) {
          var deltaX, leftWidth, rightWidth
          deltaX = e.pageX - startX
          leftWidth = startLeftWidth + deltaX
          rightWidth = startRightWidth - deltaX
          if (leftWidth < minWidth) {
            leftWidth = minWidth
            deltaX = minWidth - startLeftWidth
            rightWidth = startRightWidth - deltaX
          } else if (rightWidth < minWidth) {
            rightWidth = minWidth
            deltaX = startRightWidth - minWidth
            leftWidth = startLeftWidth + deltaX
          }
          $leftCol.attr('width', (leftWidth / tableWidth * 100) + '%')
          $rightCol.attr('width', (rightWidth / tableWidth * 100) + '%')
          return $handle.css('left', startHandleLeft + deltaX)
        })
        $(document).one('mouseup.simditor-resize-table', function(e) {
          $(document).off('.simditor-resize-table')
          return $wrapper.removeClass('resizing')
        })
        $wrapper.addClass('resizing')
        return false
      })
    }

    TableButton.prototype._initShortcuts = function() {
      this.editor.hotkeys.add('ctrl+alt+up', (function(_this) {
        return function(e) {
          _this.editMenu.find('.menu-item[data-param=insertRowAbove]').click()
          return false
        }
      })(this))
      this.editor.hotkeys.add('ctrl+alt+down', (function(_this) {
        return function(e) {
          _this.editMenu.find('.menu-item[data-param=insertRowBelow]').click()
          return false
        }
      })(this))
      this.editor.hotkeys.add('ctrl+alt+left', (function(_this) {
        return function(e) {
          _this.editMenu.find('.menu-item[data-param=insertColLeft]').click()
          return false
        }
      })(this))
      return this.editor.hotkeys.add('ctrl+alt+right', (function(_this) {
        return function(e) {
          _this.editMenu.find('.menu-item[data-param=insertColRight]').click()
          return false
        }
      })(this))
    }

    TableButton.prototype.decorate = function($table) {
      var $headRow, $tbody, $thead
      if ($table.parent('.simditor-table').length > 0) {
        this.undecorate($table)
      }
      $table.wrap('<div class="simditor-table"></div>')
      if ($table.find('thead').length < 1) {
        $thead = $('<thead />')
        $headRow = $table.find('tr').first()
        $thead.append($headRow)
        this._changeCellTag($headRow, 'th')
        $tbody = $table.find('tbody')
        if ($tbody.length > 0) {
          $tbody.before($thead)
        } else {
          $table.prepend($thead)
        }
      }
      this.initResize($table)
      return $table.parent()
    }

    TableButton.prototype.undecorate = function($table) {
      if (!($table.parent('.simditor-table').length > 0)) {
        return
      }
      return $table.parent().replaceWith($table)
    }

    TableButton.prototype.renderMenu = function() {
      var $table
      $('<div class="menu-create-table">\n</div>\n<div class="menu-edit-table">\n  <ul>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="deleteRow">\n        <span>' + (this._t('deleteRow')) + '</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="insertRowAbove">\n        <span>' + (this._t('insertRowAbove')) + ' ( Ctrl + Alt + ↑ )</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="insertRowBelow">\n        <span>' + (this._t('insertRowBelow')) + ' ( Ctrl + Alt + ↓ )</span>\n      </a>\n    </li>\n    <li><span class="separator"></span></li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="deleteCol">\n        <span>' + (this._t('deleteColumn')) + '</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="insertColLeft">\n        <span>' + (this._t('insertColumnLeft')) + ' ( Ctrl + Alt + ← )</span>\n      </a>\n    </li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="insertColRight">\n        <span>' + (this._t('insertColumnRight')) + ' ( Ctrl + Alt + → )</span>\n      </a>\n    </li>\n    <li><span class="separator"></span></li>\n    <li>\n      <a tabindex="-1" unselectable="on" class="menu-item"\n        href="javascript:;" data-param="deleteTable">\n        <span>' + (this._t('deleteTable')) + '</span>\n      </a>\n    </li>\n  </ul>\n</div>').appendTo(this.menuWrapper)
      this.createMenu = this.menuWrapper.find('.menu-create-table')
      this.editMenu = this.menuWrapper.find('.menu-edit-table')
      $table = this.createTable(6, 6).appendTo(this.createMenu)
      this.createMenu.on('mouseenter', 'td, th', (function(_this) {
        return function(e) {
          var $td, $tr, $trs, num
          _this.createMenu.find('td, th').removeClass('selected')
          $td = $(e.currentTarget)
          $tr = $td.parent()
          num = $tr.find('td, th').index($td) + 1
          $trs = $tr.prevAll('tr').addBack()
          if ($tr.parent().is('tbody')) {
            $trs = $trs.add($table.find('thead tr'))
          }
          return $trs.find('td:lt(' + num + '), th:lt(' + num + ')').addClass('selected')
        }
      })(this))
      this.createMenu.on('mouseleave', function(e) {
        return $(e.currentTarget).find('td, th').removeClass('selected')
      })
      return this.createMenu.on('mousedown', 'td, th', (function(_this) {
        return function(e) {
          var $closestBlock, $td, $tr, colNum, rowNum
          _this.wrapper.removeClass('menu-on')
          if (!_this.editor.inputManager.focused) {
            return
          }
          $td = $(e.currentTarget)
          $tr = $td.parent()
          colNum = $tr.find('td').index($td) + 1
          rowNum = $tr.prevAll('tr').length + 1
          if ($tr.parent().is('tbody')) {
            rowNum += 1
          }
          $table = _this.createTable(rowNum, colNum, true)
          $closestBlock = _this.editor.selection.blockNodes().last()
          if (_this.editor.util.isEmptyNode($closestBlock)) {
            $closestBlock.replaceWith($table)
          } else {
            $closestBlock.after($table)
          }
          _this.decorate($table)
          _this.editor.selection.setRangeAtStartOf($table.find('th:first'))
          _this.editor.trigger('valuechanged')
          return false
        }
      })(this))
    }

    TableButton.prototype.createTable = function(row, col, phBr) {
      var $table, $tbody, $td, $thead, $tr, c, k, l, r, ref, ref1
      $table = $('<table/>')
      $thead = $('<thead/>').appendTo($table)
      $tbody = $('<tbody/>').appendTo($table)
      for (r = k = 0, ref = row; ref >= 0 ? k < ref : k > ref; r = ref >= 0 ? ++k : --k) {
        $tr = $('<tr/>')
        $tr.appendTo(r === 0 ? $thead : $tbody)
        for (c = l = 0, ref1 = col; ref1 >= 0 ? l < ref1 : l > ref1; c = ref1 >= 0 ? ++l : --l) {
          $td = $(r === 0 ? '<th/>' : '<td/>').appendTo($tr)
          if (phBr) {
            $td.append(this.editor.util.phBr)
          }
        }
      }
      return $table
    }

    TableButton.prototype.refreshTableWidth = function($table) {
      var cols, tableWidth
      tableWidth = $table.width()
      cols = $table.find('col')
      return $table.find('thead tr th').each(function(i, td) {
        var $col
        $col = cols.eq(i)
        return $col.attr('width', ($(td).outerWidth() / tableWidth * 100) + '%')
      })
    }

    TableButton.prototype.setActive = function(active) {
      TableButton.__super__.setActive.call(this, active)
      if (active) {
        this.createMenu.hide()
        return this.editMenu.show()
      } else {
        this.createMenu.show()
        return this.editMenu.hide()
      }
    }

    TableButton.prototype._changeCellTag = function($tr, tagName) {
      return $tr.find('td, th').each(function(i, cell) {
        var $cell
        $cell = $(cell)
        return $cell.replaceWith('<' + tagName + '>' + ($cell.html()) + '</' + tagName + '>')
      })
    }

    TableButton.prototype.deleteRow = function($td) {
      var $newTr, $tr, index
      $tr = $td.parent('tr')
      if ($tr.closest('table').find('tr').length < 1) {
        return this.deleteTable($td)
      } else {
        $newTr = this._nextRow($tr)
        if (!($newTr.length > 0)) {
          $newTr = this._prevRow($tr)
        }
        index = $tr.find('td, th').index($td)
        if ($tr.parent().is('thead')) {
          $newTr.appendTo($tr.parent())
          this._changeCellTag($newTr, 'th')
        }
        $tr.remove()
        return this.editor.selection.setRangeAtEndOf($newTr.find('td, th').eq(index))
      }
    }

    TableButton.prototype.insertRow = function($td, direction) {
      var $newTr, $table, $tr, cellTag, colNum, i, index, k, ref
      if (direction == null) {
        direction = 'after'
      }
      $tr = $td.parent('tr')
      $table = $tr.closest('table')
      colNum = 0
      $table.find('tr').each(function(i, tr) {
        return colNum = Math.max(colNum, $(tr).find('td').length)
      })
      index = $tr.find('td, th').index($td)
      $newTr = $('<tr/>')
      cellTag = 'td'
      if (direction === 'after' && $tr.parent().is('thead')) {
        $tr.parent().next('tbody').prepend($newTr)
      } else if (direction === 'before' && $tr.parent().is('thead')) {
        $tr.before($newTr)
        $tr.parent().next('tbody').prepend($tr)
        this._changeCellTag($tr, 'td')
        cellTag = 'th'
      } else {
        $tr[direction]($newTr)
      }
      for (i = k = 1, ref = colNum; ref >= 1 ? k <= ref : k >= ref; i = ref >= 1 ? ++k : --k) {
        $('<' + cellTag + '/>').append(this.editor.util.phBr).appendTo($newTr)
      }
      return this.editor.selection.setRangeAtStartOf($newTr.find('td, th').eq(index))
    }

    TableButton.prototype.deleteCol = function($td) {
      var $newTd, $table, $tr, index, noOtherCol, noOtherRow
      $tr = $td.parent('tr')
      noOtherRow = $tr.closest('table').find('tr').length < 2
      noOtherCol = $td.siblings('td, th').length < 1
      if (noOtherRow && noOtherCol) {
        return this.deleteTable($td)
      } else {
        index = $tr.find('td, th').index($td)
        $newTd = $td.next('td, th')
        if (!($newTd.length > 0)) {
          $newTd = $tr.prev('td, th')
        }
        $table = $tr.closest('table')
        $table.find('col').eq(index).remove()
        $table.find('tr').each(function(i, tr) {
          return $(tr).find('td, th').eq(index).remove()
        })
        this.refreshTableWidth($table)
        return this.editor.selection.setRangeAtEndOf($newTd)
      }
    }

    TableButton.prototype.insertCol = function($td, direction) {
      var $col, $newCol, $newTd, $table, $tr, index, tableWidth, width
      if (direction == null) {
        direction = 'after'
      }
      $tr = $td.parent('tr')
      index = $tr.find('td, th').index($td)
      $table = $td.closest('table')
      $col = $table.find('col').eq(index)
      $table.find('tr').each((function(_this) {
        return function(i, tr) {
          var $newTd, cellTag
          cellTag = $(tr).parent().is('thead') ? 'th' : 'td'
          $newTd = $('<' + cellTag + '/>').append(_this.editor.util.phBr)
          return $(tr).find('td, th').eq(index)[direction]($newTd)
        }
      })(this))
      $newCol = $('<col/>')
      $col[direction]($newCol)
      tableWidth = $table.width()
      width = Math.max(parseFloat($col.attr('width')) / 2, 50 / tableWidth * 100)
      $col.attr('width', width + '%')
      $newCol.attr('width', width + '%')
      this.refreshTableWidth($table)
      $newTd = direction === 'after' ? $td.next('td, th') : $td.prev('td, th')
      return this.editor.selection.setRangeAtStartOf($newTd)
    }

    TableButton.prototype.deleteTable = function($td) {
      var $block, $table
      $table = $td.closest('.simditor-table')
      $block = $table.next('p')
      $table.remove()
      if ($block.length > 0) {
        return this.editor.selection.setRangeAtStartOf($block)
      }
    }

    TableButton.prototype.command = function(param) {
      var $td
      $td = this.editor.selection.containerNode().closest('td, th')
      if (!($td.length > 0)) {
        return
      }
      if (param === 'deleteRow') {
        this.deleteRow($td)
      } else if (param === 'insertRowAbove') {
        this.insertRow($td, 'before')
      } else if (param === 'insertRowBelow') {
        this.insertRow($td)
      } else if (param === 'deleteCol') {
        this.deleteCol($td)
      } else if (param === 'insertColLeft') {
        this.insertCol($td, 'before')
      } else if (param === 'insertColRight') {
        this.insertCol($td)
      } else if (param === 'deleteTable') {
        this.deleteTable($td)
      } else {
        return
      }
      return this.editor.trigger('valuechanged')
    }

    return TableButton
  })(Button)

  Simditor.Toolbar.addButton(TableButton)

  StrikethroughButton = (function(superClass) {
    extend(StrikethroughButton, superClass)

    function StrikethroughButton() {
      return StrikethroughButton.__super__.constructor.apply(this, arguments)
    }

    StrikethroughButton.prototype.name = 'strikethrough'

    StrikethroughButton.prototype.icon = 'strikethrough'

    StrikethroughButton.prototype.htmlTag = 'strike'

    StrikethroughButton.prototype.disableTag = 'pre'

    StrikethroughButton.prototype._activeStatus = function() {
      var active
      active = document.queryCommandState('strikethrough') === true
      this.setActive(active)
      return this.active
    }

    StrikethroughButton.prototype.command = function() {
      document.execCommand('strikethrough')
      if (!this.editor.util.support.oninput) {
        this.editor.trigger('valuechanged')
      }
      return $(document).trigger('selectionchange')
    }

    return StrikethroughButton
  })(Button)

  Simditor.Toolbar.addButton(StrikethroughButton)

  AlignmentButton = (function(superClass) {
    extend(AlignmentButton, superClass)

    function AlignmentButton() {
      return AlignmentButton.__super__.constructor.apply(this, arguments)
    }

    AlignmentButton.prototype.name = 'alignment'

    AlignmentButton.prototype.icon = 'align-left'

    AlignmentButton.prototype.htmlTag = 'p, h1, h2, h3, h4, td, th'

    AlignmentButton.prototype._init = function() {
      this.menu = [
        {
          name: 'left',
          text: this._t('alignLeft'),
          icon: 'align-left',
          param: 'left'
        }, {
          name: 'center',
          text: this._t('alignCenter'),
          icon: 'align-center',
          param: 'center'
        }, {
          name: 'right',
          text: this._t('alignRight'),
          icon: 'align-right',
          param: 'right'
        }
      ]
      return AlignmentButton.__super__._init.call(this)
    }

    AlignmentButton.prototype.setActive = function(active, align) {
      if (align == null) {
        align = 'left'
      }
      if (align !== 'left' && align !== 'center' && align !== 'right') {
        align = 'left'
      }
      if (align === 'left') {
        AlignmentButton.__super__.setActive.call(this, false)
      } else {
        AlignmentButton.__super__.setActive.call(this, active)
      }
      this.el.removeClass('align-left align-center align-right')
      if (active) {
        this.el.addClass('align-' + align)
      }
      this.setIcon('align-' + align)
      return this.menuEl.find('.menu-item').show().end().find('.menu-item-' + align).hide()
    }

    AlignmentButton.prototype._status = function() {
      this.nodes = this.editor.selection.nodes().filter(this.htmlTag)
      if (this.nodes.length < 1) {
        this.setDisabled(true)
        return this.setActive(false)
      } else {
        this.setDisabled(false)
        return this.setActive(true, this.nodes.first().css('text-align'))
      }
    }

    AlignmentButton.prototype.command = function(align) {
      if (align !== 'left' && align !== 'center' && align !== 'right') {
        throw new Error('simditor alignment button: invalid align ' + align)
      }
      this.nodes.css({
        'text-align': align === 'left' ? '' : align
      })
      this.editor.trigger('valuechanged')
      return this.editor.inputManager.throttledSelectionChanged()
    }

    return AlignmentButton
  })(Button)

  Simditor.Toolbar.addButton(AlignmentButton)

  return Simditor
}))
